Search code examples
javascriptreactjsreact-nativereact-component

React Native -- .Map() items in Child won't rerender after Child event handler updates Parent's state


In my project, while looping through a datafile with the .map() function, I'm passing down a function (updatestate) from the Parent Component, App, to the Child, Main, that allows the Child to call .setState() and append to the array in the Parent state.

But, when I call this function to set Parent's state, the mapped section -- {contents} -- won't rerender unless I tap the screen again.

Any ideas? Am I missing some binding or need to do some componentDidMount or anything? I've tried adding this.forceUpdate() inside the eventHandler but the **mapped {contents} section ** still won't render automatically.

const RootStack = StackNavigator(
  {
    Main: {
      screen: Main}
  }
);

export default class App extends Component<{}> {
    constructor(props) {
      super(props);
      this.state = {
        favoritesList: []
      };
    }

  updateArr=(itemname, url)=>{this.setState({ favoritesList: [...this.state.favoritesList, {"item_name":itemname, "url":url}]})};

  render() {
      return <RootStack screenProps={{appstate: this.state,
                                    updatestate: this.updateArr}}
      />;
    }
  }

class Main extends React.Component {

  render() {
    var updatestate = this.props.screenProps.updatestate;

    //collection is the data (local JSON) i'm looping thru via .map in Main Component

    const contents = collection.map((item, index) => {
        return (
            <Card key={index}
                  onSwipedRight={() => {updatestate(item.item_name,item.url)}}
            >
              <View> //THIS and anything after <Card> doesn't render for the next card automatically if I do 'onSwipedRight'
                <Image
                    source={{uri: item.url}} />
               </View>
            </Card>
        )
      },this);

      return (
      <View>
            <CardStack>

              {contents} //THIS IS THE PART THAT WON'T AUTO-RERENDER AFTER THE EVENT HANDLER THAT SETS THE PARENT'S STATE

            </CardStack>
      </View>
        );
    }
}

To make sure it's not an issue with the event handler, I added in a function that doesn't set the state of the parent, and had <Card> call that on the event handler -- it works perfectly and the child component <Card> renders perfectly. It seems that it's the updatestate function passed down from the parent to the child that acts to call .setState() upstream that for some reason is causing the Child to not render/not finish rendering the mapped {contents} after the event handler.

class Main extends React.Component {
  render() {

    var updatestate = this.props.screenProps.updatestate;

    var newfunc = (a, b) => {console.log('a:', a, 'b:', b)};

      const contents = collection.map((item, index) => {
        return (
            <Card key={index}
                  newfunc(item.item_name,item.item_name);}}
                  // onSwipedRight={() => {updatestate(item.item_name,item.url); }}
                  >

Solution

  • Try rewriting your setState like so

    import { InteractionManager } from "react-native";
    
    updateArr = (itemname, url) => {
      InteractionManager.runAfterInteractions(() => {
        this.setState( prevState => ({
          favoritesList: [...prevState.favoritesList, {       "item_name":itemname, "url":url } ]
        });
      });
      
    }
    
    // add this line to your parent component constructor()
    this.updateArr = this.updateArr.bind(this);

    In addition I suggest you change your React.Component to React.PureComponent instead it will handle the shallow equal of your state changes for you and increate performance. for reference React.Component vs React.PureComponent