admin管理员组

文章数量:1290946

I am having problem figuring out why my application is doing endless render.

Inside, My stateful ponent, I am calling a redux action in ponentDidMount method (calling ponentWillMount also do endless render)

class cryptoTicker extends PureComponent {
  ponentDidMount() {
    this.props.fetchCoin()
    // This fetches some 1600 crypto coins data,Redux action link for the same in end
  }

  render() {
    return (
      <ScrollView>
        <Header />
        <View>
          <FlatList
            data={this.state.searchCoin ? this.displaySearchCrypto : this.props.cryptoLoaded}
            style={{ flex: 1 }}
            extraData={[this.displaySearchCrypto, this.props.cryptoLoaded]}
            keyExtractor={item => item.short}
            initialNumToRender={50}
            windowSize={21}
            removeClippedSubviews={true}
            renderItem={({ item, index }) => (
              <CoinCard
                key={item["short"]}
              />
            )} 
          />
        </View>
      </ScrollView>
    )
  }
}

In CoinCard I am literally doing nothing besides this (Notice CoinCard inside Flat list)

class CoinCard extends Component {
  render () { 
    console.log("Inside rende here")
    return (
        <View> <Text> Text </Text>  </View>
    )  
  }
}

Now, When I console log in my coincard render, I can see infinite log of Inside rende here

[Question:] Can anyone please help me figure out why this could be happening?

You can click here to see my actions and click here to see my reducer.

[Update:] My repository is here if you want to clone and see it by yourself.

[Update: 2]: I have pushed the above shared code on github and it will still log endless console.log statements (if you can clone, run and move back to this mit ).

[Update:3]: I am no longer using <ScrollView /> in <FlatList /> also when I mean endless render, I mean is that it is endless (& Unecessarily) passing same props to child ponent (<Coincard />), if I use PureComponent, it won't log endlessly in render () { but in ponentWillRecieveProps, If I do console.log(nextProps), I can see the same log passed over and over again

I am having problem figuring out why my application is doing endless render.

Inside, My stateful ponent, I am calling a redux action in ponentDidMount method (calling ponentWillMount also do endless render)

class cryptoTicker extends PureComponent {
  ponentDidMount() {
    this.props.fetchCoin()
    // This fetches some 1600 crypto coins data,Redux action link for the same in end
  }

  render() {
    return (
      <ScrollView>
        <Header />
        <View>
          <FlatList
            data={this.state.searchCoin ? this.displaySearchCrypto : this.props.cryptoLoaded}
            style={{ flex: 1 }}
            extraData={[this.displaySearchCrypto, this.props.cryptoLoaded]}
            keyExtractor={item => item.short}
            initialNumToRender={50}
            windowSize={21}
            removeClippedSubviews={true}
            renderItem={({ item, index }) => (
              <CoinCard
                key={item["short"]}
              />
            )} 
          />
        </View>
      </ScrollView>
    )
  }
}

In CoinCard I am literally doing nothing besides this (Notice CoinCard inside Flat list)

class CoinCard extends Component {
  render () { 
    console.log("Inside rende here")
    return (
        <View> <Text> Text </Text>  </View>
    )  
  }
}

Now, When I console log in my coincard render, I can see infinite log of Inside rende here

[Question:] Can anyone please help me figure out why this could be happening?

You can click here to see my actions and click here to see my reducer.

[Update:] My repository is here if you want to clone and see it by yourself.

[Update: 2]: I have pushed the above shared code on github and it will still log endless console.log statements (if you can clone, run and move back to this mit ).

[Update:3]: I am no longer using <ScrollView /> in <FlatList /> also when I mean endless render, I mean is that it is endless (& Unecessarily) passing same props to child ponent (<Coincard />), if I use PureComponent, it won't log endlessly in render () { but in ponentWillRecieveProps, If I do console.log(nextProps), I can see the same log passed over and over again

Share Improve this question edited Sep 10, 2018 at 14:25 n1stre 6,0864 gold badges23 silver badges42 bronze badges asked Sep 7, 2018 at 0:05 AlwaysblueAlwaysblue 11.9k44 gold badges141 silver badges252 bronze badges 15
  • 1 Not confident enough to post a full answer, but I think your problem might be here. When you update the props of that ponent, I think ponentDidUpdate will fire, which will update the props again, etc. This will also cause FlatList's data prop to change, which will render it each time as well, causing infinite renders. – izb Commented Sep 7, 2018 at 0:24
  • 2 Please include the relevant code in the question. – Felix Kling Commented Sep 7, 2018 at 0:28
  • @izb thanks a lot for your answer! I removed ponentDidUpdate , in-fact I removed everything from my code and boiled it down to what I have shared above but still I can see those endless logs :\ – Alwaysblue Commented Sep 7, 2018 at 0:29
  • 2 There may be more than one thing wrong with your project. Specifically you should never assign to props github./irohitb/Crypto/blob/… – Andy Ray Commented Sep 7, 2018 at 0:31
  • 1 The actions & reducers are fine. If I strip away all views/lists/etc, just render the ponents, and remove all state update functions, everything only renders twice. This seems specific to React Native, which I am not an expert in. I've added the react-native tag to your question, so hopefully that helps bring the right folks. Good luck, seems like a really tricky issue. I suspect you'll want to use a virtualized list, that the re-rendering is caused by layout issues. – Jacob Commented Sep 7, 2018 at 22:51
 |  Show 10 more ments

3 Answers 3

Reset to default 3 +25

There are some points to note in your code.

  • The CoinCard Component must be a PureComponent, which will not re-render if the props are shallow-equal.
  • You should not render your Flatlist inside the ScrollView ponent, which would make the ponent render all ponents inside it at once which may cause more looping between the Flatlist and ScrollView.
  • You can also a definite height to the rendered ponent to reduce the number of times ponent is rendered for other props.
  • Another thing to note is, only props in the ponent are rendered on scroll bottom, based on the log statement mentioned below.

    import {Dimensions} from 'react-native'
    
    const {width, height} = Dimensions.get('window)
    
    class CoinCard extends React.PureComponent {
    render () { 
      console.log(this.props.item.long)  //... Check the prop changes here, pass the item prop in parent Flatlist. This logs ponent prop changes which will show that same items are not being re-rendered but new items are being called.
      return (
        <View style={{height / 10, width}}> //... Render 10 items on the screen
          <Text>
            Text
          </Text>
        </View>
      )  
     }
    }
    

UPDATE

This extra logging is due to the props being from the Flatlist to your ponent without PureComponent shallow parison.

Note that ponentWillReceiveProps() is deprecated and you should avoid them in your code. React.PureComponent works under the hood and uses shouldComponentUpdate to use shallow parison between the current and updated props. Therefore log console.log(this.props.item.long) in your PureComponent' render will log the unique list which can be checked.

Like izb mentions, the root cause of the pb is the business call that is done on a pure ponent whereas it is just loaded. It is because your ponent make a business decision (<=>"I decide when something must be showed in myself"). It is not a good practice in React, even less when you use redux. The ponent must be as stupid a possible and not even decide what to do and when to do it.

As I see in your project, you don't deal correctly with ponent and container concept. You should not have any logic in your container, as it should simply be a wrapper of a stupid pure ponent. Like this:

import { connect, Dispatch } from "react-redux";
import { push, RouterAction, RouterState } from "react-router-redux";
import ApplicationBarComponent from "../ponents/ApplicationBar";

export function mapStateToProps({ routing }: { routing: RouterState }) {
    return routing;
}

export function mapDispatchToProps(dispatch: Dispatch<RouterAction>) {
    return {
        navigate: (payload: string) => dispatch(push(payload)),
    };
}
const tmp = connect(mapStateToProps, mapDispatchToProps);
export default tmp(ApplicationBarComponent);

and the matching ponent:

import AppBar from '@material-ui/core/AppBar';
import IconButton from '@material-ui/core/IconButton';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import { StyleRules, Theme, withStyles, WithStyles } from '@material-ui/core/styles';
import Tab from '@material-ui/core/Tab';
import Tabs from '@material-ui/core/Tabs';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import AccountCircle from '@material-ui/icons/AccountCircle';
import MenuIcon from '@material-ui/icons/Menu';
import autobind from "autobind-decorator";
import * as React from "react";
import { push, RouterState } from "react-router-redux";

const styles = (theme: Theme): StyleRules => ({
  flex: {
    flex: 1
  },
  menuButton: {
    marginLeft: -12,
    marginRight: 20,
  },
  root: {
    backgroundColor: theme.palette.background.paper,
    flexGrow: 1
  },
});
export interface IProps extends RouterState, WithStyles {
  navigate: typeof push;
}

@autobind
class ApplicationBar extends React.PureComponent<IProps, { anchorEl: HTMLInputElement | undefined }> {
  constructor(props: any) {
    super(props);
    this.state = { anchorEl: undefined };
  }
  public render() {

    const auth = true;
    const { classes } = this.props;
    const menuOpened = !!this.state.anchorEl;
    return (
      <div className={classes.root}>
        <AppBar position="fixed" color="primary">
          <Toolbar>
            <IconButton className={classes.menuButton} color="inherit" aria-label="Menu">
              <MenuIcon />
            </IconButton>
            <Typography variant="title" color="inherit" className={classes.flex}>
              Title
            </Typography>

            <Tabs value={this.getPathName()} onChange={this.handleNavigate} >
              {/* <Tabs value="/"> */}
              <Tab label="Counter 1" value="/counter1" />
              <Tab label="Counter 2" value="/counter2" />
              <Tab label="Register" value="/register" />
              <Tab label="Forecast" value="/forecast" />
            </Tabs>

            {auth && (
              <div>
                <IconButton
                  aria-owns={menuOpened ? 'menu-appbar' : undefined}
                  aria-haspopup="true"
                  onClick={this.handleMenu}
                  color="inherit"
                >
                  <AccountCircle />
                </IconButton>
                <Menu
                  id="menu-appbar"
                  anchorEl={this.state.anchorEl}
                  anchorOrigin={{
                    horizontal: 'right',
                    vertical: 'top',
                  }}
                  transformOrigin={{
                    horizontal: 'right',
                    vertical: 'top',
                  }}
                  open={menuOpened}
                  onClose={this.handleClose}
                >
                  <MenuItem onClick={this.handleClose}>Profile</MenuItem>
                  <MenuItem onClick={this.handleClose}>My account</MenuItem>
                </Menu>
              </div>
            )}
          </Toolbar>
        </AppBar>
      </div >
    );
  }
  private getPathName(): string {
    if (!this.props.location) {
      return "/counter1";
    }
    return (this.props.location as { pathname: string }).pathname;
  }
  private handleNavigate(event: React.ChangeEvent<{}>, value: any) {
    this.props.navigate(value as string);
  }

  private handleMenu(event: React.MouseEvent<HTMLInputElement>) {
    this.setState({ anchorEl: event.currentTarget });
  }

  private handleClose() {
    this.setState({ anchorEl: undefined });
  }
}
export default withStyles(styles)(ApplicationBar);

Then you will tell me: "but where do I initiate the call that will fill my list?" Well I see here that you use redux-thunk (I prefer redux observable... more plicated to learn but waaaaaaaaaaaaaaaaay more powerful), then this should be thunk that initiates the dispatch of this!

To summarize:

  • Components: the stupidest element that normally should have only the render method, and some other method handler to bubble up user events. This method only takes care of showing its properties to the user. Don't use the state unless you have a visual information that belongs only to this ponent (like the visibility of a popup for example). Anything that is showed or updated es from above: a higher level ponent, or a container. It doesn't decide to update its own values. At best, it handles a user event on a subponent, then bubble up another event above, and... well maybe at some point, some new properties will be given back by its container!
  • Container: very stupid logic that consists in wrapping a top level ponent into redux for it to plug events to actions, and to plug some part of the store to properties
  • Redux thunk (or redux observable): it is the one that handles the whole user application logic. This guy is the only one who knows what to trigger and when. If a part of your front end must contain the plexity, it's this one!
  • Reducers: define how to organize the data in the store for it to be as easily usable as possible.
  • The store: ideally one per top level container, the only one that contains the data that must be showed to the user. Nobody else should.

If you follow these principles, you should never face any issue like "why the hell this is called twice? and... who made it? and why at this moment?"

Something else: if you use redux, use an immutability framework. Otherwise you may face issues as reducers must be pure functions. For this you can use a popular one immutable.js but not convenient at all. And the late ousider that is actually a killer: immer (made by the author or mobx).

It seems Jacob in the above ment has managed to make the ponent render only twice.

This will definitely cause double initial render (and would cause an infinite render if it wasn't a PureComponent):

ponentDidUpdate() {
    var updateCoinData;

    if (!updateCoinData) { // <- this is always true
        updateCoinData = [...this.props.cryptoLoaded];
        this.setState({updateCoinData: true}); // <- this will trigger a re render since `this.state.updateCoinData` is not initially true
    }
    ...
}

Link to the issue in your repository

本文标签: javascriptinfinite Render in ReactStack Overflow