简体   繁体   中英

React native navigation - Child not updating on parent update

I've edited a little bit the example from the react navigation official guide , in order to simulate a state change after 1 second in the HomeScreen.

I can't understand why the DetailScreen is not rerenderer when the parent screen state changes. Any way to obtain such behaviour?

import React from 'react';
import { Button, View, Text } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';

class HomeScreen extends React.Component {
  state = { value: 10 }
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Home Screen</Text>
        <Button
          title="Click"
          onPress={() => {
            const { value } = this.state;
            this.props.navigation.navigate('Detail', { value });
            setTimeout(() => {
              this.setState({ value: value + 1 })
            }, 1000);
          }}
        />
      </View>
    );
  }
}

class DetailScreen extends React.Component {
  render() {
    const { navigation } = this.props;
    const value = navigation.getParam('value');
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Detail Screen</Text>
        <Text>{value}</Text>
      </View>
    );
  }
}

const RootStack = createStackNavigator(
  {
    Home: HomeScreen,
    Detail: DetailScreen,
  },
  {
    initialRouteName: 'Home',
  }
);

const AppContainer = createAppContainer(RootStack);

export default class App extends React.Component {
  render() {
    return <AppContainer />;
  }
}

Snack here

Why your code isn't working:

You're passing value as a navigation prop from Home to Details . Unlike regular props, changing the value of a navigation prop in Home doesn't cause the value of the navigation prop itself to change. So if Home were a parent component that had Details as a child component, like this:

class HomeScreen extends React.Component {
  ...
  <DetailsScreen
    value: this.state.value
  />
  ...
}

then when this.state.value changes in Home , this.props.value automatically changes in Details . However, since Home and Details have a sibling relationship in the stack navigator, you aren't able to pass value as a regular prop; the only way to pass a prop from Home to Details is, as you've done, as a navigation param. The problem is that when you pass value as you've done:

const { value } = this.state;
this.props.navigation.navigate('Detail', { value });

updating this.state.value in Home does not cause this.props.navigation.getParam('value') to automatically update. So in your code:

const value = navigation.getParam('value');
<Text>{value}</Text>

value remains what it was when it was initially passed.

SOLUTION

There are several possible workarounds, like manually forcing a re-render after the setTimeout or re-structuring your component hierarchy to make Home the parent of Details . However, I think the best way to solve the problem while preserving the structure of your app is the following:

Instead of holding this.state.value in Home , hold it in App . This follows the general principle that it's easier for a child to update a parent's state variables (or vice versa) than it is for a component to update its sibling's state variables.

Update to App component

Since App is an AppContainer , you'll need to pass this.state.value to Details via screenprops. When you create any kind of navigator, screenprops are the way to pass variables to all the components in the navigator. So your App component will now look like this:

export default class App extends React.Component {

  state = {value: 10} // state in App now instead of Home

  updateValue = (value) => { // method to update App's state, passed to children
    this.setState({value})
  }

  render() {
    return <AppContainer screenProps={{
      parentState: this.state,
      updateValue: this.updateValue
    }}/>;
  }

}

Update to Home component

The only thing you'll change in your Home component will be the onPress function. First, you won't pass value as a navigation prop anymore since you'll be accessing the value as a screenProps passed from App to all screens, rather than as a navigation prop passed from Home to Details . Second, instead of updating this.state of Home you'll be calling this.props.screenProps.updateValue() to update the state in App . So the onPress in your Home component will now look like this:

onPress={() => {
  this.props.navigation.navigate('Detail'); // no navigation prop
  setTimeout(() => {
    screenProps.updateValue(screenProps.appState.value + 1) // updating state of App rather than state of Home
  }, 1000);
}}

Update to Details component

The only change to Details is that rather than displaying this.props.navigation.getParam('value') , you'll be displaying this.props.screenProps.appState.value since we're now getting value from the screenProps from App instead of as a navigation prop from Home . So your Details component will now look like this:

class DetailScreen extends React.Component {
  render() {
    const { screenProps } = this.props;
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Detail Screen</Text>
        <Text>{screenProps.appState.value}</Text> 
      </View>
    );
  }
}

WHOLE NEW CODE

import React from 'react';
import { Button, View, Text } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';

class HomeScreen extends React.Component {
  state = { value: 10 }
  render() {
    const { screenProps } = this.props;
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Home Screen</Text>
        <Button
          title="Click"
          onPress={() => {
            this.props.navigation.navigate('Detail'); // no navigation prop
            setTimeout(() => {
              screenProps.updateValue(screenProps.appState.value + 1) // updating state of App rather than state of Home
            }, 1000);
          }}
        />
      </View>
    );
  }
}

class DetailScreen extends React.Component {
  render() {
    const { screenProps } = this.props;
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Detail Screen</Text>
        <Text>{screenProps.appState.value}</Text>
      </View>
    );
  }
}

const RootStack = createStackNavigator(
  {
    Home: HomeScreen,
    Detail: DetailScreen,
  },
  {
    initialRouteName: 'Home',
  }
);

const AppContainer = createAppContainer(RootStack);

export default class App extends React.Component {

  state = {value: 10} // state in App now instead of Home

  updateValue = (value) => { // method to update App's state, passed to children
    this.setState({value})
  }

  render() {
    return <AppContainer screenProps={{
      appState: this.state,
      updateValue: this.updateValue
    }}/>;
  }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM