简体   繁体   中英

Weird behavior of Redux thunk in a React Native app

I recently noticed a very weird behavior of Redux in my app that I would like to share with you. When I click this TouchableOpacity in my settings in order to disconnect, I get the following error:

TypeError: Cannot read property first_name of null

from Homepage.js

So my code is firing an error for that piece component that is in a page where I'm not supposed to go.

Homepage.js

<AppText textStyle={styles.title}>
  Welcome {userReducer.infos.first_name}
</AppText>

The workflow that is supposed to happen is the user clicking on the disconnect button, then the tryLogout function is called, which fires the action with the type 'LOGOUT'. Then this action sets to null user related data and information with some additional booleans. The state gets updated then the UI should be re-rendered and componentWillUpdate() called. In there, I check my state to redirect the user to the landing/not connected navigation.

I tried to comment infos: null in my reducer and my code processes without errors.

This variable is nowhere in my app asking to redirect to any page.

It may seem complicated to debug, since the problem must be deeper than it looks. Anyway, maybe someone has already had a similar problem so I give you here the potential elements to try to solve the problem.

Settings.js

import { tryLogout } from '@actions/user'

const mapDispatchToProps = dispatch => ({
  tryLogout: () => dispatch(tryLogout()),
})

class Settings extends React.Component<Props, State> {  

  // What should normally happen
  componentDidUpdate(prevProps: Props) {
      const { userReducer, navigation } = this.props

      if (
        prevProps.userReducer.isSignedUp !== userReducer.isSignedUp ||
        prevProps.userReducer.isLoggedIn !== userReducer.isLoggedIn
      ) {
        if (!userReducer.isSignedUp && !userReducer.isLoggedIn) {
          navigation.navigate('Landing')
        }
      }
    }

  // Inside my render() method
  <AppTouchableOpacity
          onPress={() => tryLogout()}
          style={styles.touchableOpacity}
        >
          Disconnect
  </AppTouchableOpacity>
}

actions/user.js

export const tryLogout = (): Function => (dispatch: Function) => {
  const logout = () => ({
    type: LOGOUT,
  })

  dispatch(logout())
}

reducers/user.js

case LOGOUT:
  return {
    ...state,
    data: null,
    infos: null,
    isLoggedIn: false,
    isSignedUp: false,
  }

App.js (I'm using React Navigation)

const LandingScenes = {
  Welcome: { screen: WelcomeScreen },
  { /* Some other screens */ }
}

const LandingNavigator = createStackNavigator(LandingScenes, {
  initialRouteName: 'Welcome',
  defaultNavigationOptions: {
    header: null,
  },
})

const SecuredScenes = {
  Homepage: { screen: HomepageScreen },
  Settings: { screen: SettingsScreen },
  { /* Some other screens */ }
}
const SecuredNavigator = createStackNavigator(SecuredScenes, {
  initialRouteName: 'Homepage',
  defaultNavigationOptions: {
    header: null,
  },
})

export default createAppContainer(
  createSwitchNavigator(
    {
      Landing: LandingNavigator,
      Secured: SecuredNavigator,
    },
    {
      initialRouteName: 'Landing',
    }
  )
)

If I understand correctly, the issue is that the Homepage component isn't expected to be loaded when the user signs out, but is throwing a null error on infos . Since you are using a stack navigator it is likely that your Homepage component is actually still mounted in the stack. You will need to either handle the null case in your Homepage or remove it from the stack during navigation to Settings .

You can find more details about the lifecycle in react-navigation in their documentation here: Navigation lifecycle - Example scenario , the important part being here:

We start on the HomeScreen and navigate to DetailsScreen. Then we use the tab bar to switch to the SettingsScreen and navigate to ProfileScreen. After this sequence of operations is done, all 4 of the screens are mounted !

Sounds like it's trying to render, when the value of 'infos' is null.

This makes sense, as the homescreen is still living in main memory, in your navigation stack, this is because you're using the 'navigate' method when navigating to settings.

So this is a result of using react-navigation, not redux, or redux thunk.

To handle this error case in your view, you could not render the text unless infos is defined. Like below as an example, you could of course display a loader, or whatever if infos is undefined/null, instead of nothing, or even a default message.

Really, the final answer here is restructuring your navigation. You could look at using a different method to navigate to the settings page, which resets the stack navigator, or keep it handled in the view, both is the ideal.

Look at the replace method for instance, this is used to reset the navigation stack, or even look at the switch navigator, which is made to only show one screen (where a screen can be a stack navigator, ie many screens) at a time, creating a separation between contexts within your app.

{userReducer.infos && (
     <AppText textStyle={styles.title}>
       Welcome {userReducer.infos.first_name}
     </AppText>
)}

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