在 redux 状态更改后,React 导航不会更改导航屏幕

Posted

技术标签:

【中文标题】在 redux 状态更改后,React 导航不会更改导航屏幕【英文标题】:React navigation doesn't change navigation screen after redux state changes 【发布时间】:2021-07-21 00:16:59 【问题描述】:

即使在 redux 状态更改后,导航也不会更改为 MainScreen。我已经验证 authState 从 "isAuthenticated": false, "isLoading": true, "token": null 更改为 "isAuthenticated": true, "isLoading": false, "token": "some_token",但是导航页面停留在登录页面(在 LandingNavigator 内),而不是转到 MainNavigator。

AppNavigation.js

const Navigator = () =>  

  var [authStat, setAuthStat] = useState()

  useEffect(()=> 
    var authState = store.getState().auth
    setAuthStat(authState)
  , [authStat])
  console.log(authStat);


  if(authStat.isLoading)
    return(
      <Stack.Navigator>
      <Stack.Screen
        options=headerShown: false
        name="Splash"
        component=ShowSplash
      />
      </Stack.Navigator>
    )
  
  else
    return(
      <Stack.Navigator>
         authStat.isAuthenticated ?
          (<Stack.Screen
            options=headerShown: false
            name="Main"
            component=MainNavigator
          />)
          :
          (<Stack.Screen options=headerShown: false name="Landing" component=LandingNavigator />)
          
      </Stack.Navigator>
    )
  

;

AuthAction.js

export function loginRequest() 
    return 
        type: "LOGIN_REQUEST",
    ;


export function loginSuccess(data) 
    return 
        type: "LOGIN_SUCCESS",
        payload: data
    ;


export function loginFailure(data) 
    return 
        type: "LOGIN_FAILURE",
        payload: data
    ;


export function restoreToken(data) 
    return 
        type: "RESTORE_TOKEN",
        payload: data
    ;


export function logOut() 
    return 
        type: "LOGOUT",
    ;

AuthReducer.js

/* eslint-disable comma-dangle */
const authState = 
    isLoading: true,
    isAuthenticated: false,
    token: null
  ;
  
  export const authReducer = (state = authState, action) => 
    const newState = JSON.parse(JSON.stringify(state));
    switch (action.type) 
        case 'LOGIN_REQUEST': 
            return 
                isLoading: true, // Show a loading indicator.
                isAuthenticated: false
            
        
        case 'RESTORE_TOKEN': 
            return 
                isLoading: false, // Show a loading indicator.
                isAuthenticated: true,
                token: action.payload
            
        
        case 'LOGIN_FAILURE':
        return 
            isLoading: false,
            isAuthenticated: false,
            error: action.error
        
        case 'LOGIN_SUCCESS':
        return 
            isLoading: false,
            isAuthenticated: true, // Dismiss the login view.
            token: action.payload
        
        case 'LOGOUT': 
            return 
                isLoading: false, // Show a loading indicator.
                isAuthenticated: false,
                token: null
            
        
      default:
        return newState;
    
  
    return newState;
  ;

Auth.js

import AsyncStorage from '@react-native-async-storage/async-storage';
import  useDispatch  from 'react-redux';
import loginRequest, loginSuccess, loginFailure, logOut from '../redux/actions/authAction';

export const storeToken = async (value) => 
    try 
      await AsyncStorage.setItem('token', value)
     catch (e) 
      // saving error
    
  

  export const getToken = async () => 
    try 
      const value = await AsyncStorage.getItem('token')
      if(value !== null) 
        return value
       else
          return null
      
     catch(e) 
      // error reading value
      console.log(e);
    
  

  export const removeToken = async () => 
    try 
      await AsyncStorage.removeItem('token')
     catch(e) 
      // remove error
    
    console.log('token removed.')
  

  export const isLoggedIn = async () => 
    if(await getToken() != null)
        return true
    
    return false


export const signOut = () => 
  removeToken()


export default storeToken, getToken, removeToken, isLoggedIn, signOut 

LoginScreen.js

/* eslint-disable comma-dangle */
import React,  useEffect, useState, useCallback  from 'react';
import 
  View,
  TouchableHighlight,
  Text,
  TextInput,
  TouchableWithoutFeedback,
  Keyboard,
  ScrollView
 from 'react-native';
import login from '../../api/apiQueries'
import storeToken from '../../auth/auth'
import store from '../../redux/store';
import styles from './styles';

import  useDispatch, useSelector  from 'react-redux';
const authState = store.getState().auth;


const LogInScreen = (route, navigation) => 

  const [userName, setUserName] = useState("")
  const [password, setPassword] = useState("")

  const dispatch = useDispatch()

  onPressLogButton = () => 
    dispatch(login(userName, password))
  

  return (
  <TouchableWithoutFeedback onPress=() => Keyboard.dismiss()>
    <ScrollView style=styles.container>
      <View>
        <Text style=styles.title>Sign in</Text>
        <View style=styles.inputContainer>
          <TextInput
            style=styles.input
            placeholder="User Name"
            onChangeText=text => setUserName(text)
            value=userName
          />
        </View>
        <View style=styles.inputContainer>
          <TextInput
            style=styles.input
            placeholder="Password"
            onChangeText=text => setPassword(text)
            value=password
          />
        </View>
        <View style=styles.logContainer>
          <TouchableHighlight
            style=styles.loginContainer
            onPress=() => onPressLogButton()
          >
            <Text style=styles.logTxt>Log in</Text>
          </TouchableHighlight>
          /* <Text style=styles.orTxt>OR</Text> */
          /* <TouchableHighlight
            style=styles.facebookContainer
            onPress=() => this.onPressFacebookButton()
          >
            <Text style=styles.facebookTxt>Facebook Login</Text>
          </TouchableHighlight> */
        </View>
      </View>
    </ScrollView>
  </TouchableWithoutFeedback>
);




export default LogInScreen

【问题讨论】:

你正在使用钩子,你正在使用 react-redux;您为什么不使用useSelector 而不是直接访问商店并将商店详细信息手动复制到本地状态?这样,一旦 redux 状态发生变化,您就不会得到任何更新。 黄金!我刚刚用 redux store 替换了本地状态,并使用了你提到的 use 选择器,它起作用了。想知道使用 getStore 和 useSelector 获得商店有什么区别? 请检查我的回答;如果您接受,我将不胜感激。 【参考方案1】:

正如 cmets 中所讨论的,解决方案是使用 useSelector 挂钩,或者使用 connect 方法的 mapStateToProps 参数订阅您的组件以存储更新。这样,只要商店通过分派的操作更新,它就会运行。

来自文档:

useSelector() 也会订阅 Redux 存储,并运行你的 每当调度一个动作时选择器。 Link

这意味着,例如,对于您的 AppNavigation.js,您可以将代码更改为:

import  useSelector  from 'react-redux';

const Navigator = () =>  
    const authStat = useSelector((state) => state.auth);

    if(authStat.isLoading)
        return(
...

通过直接访问从存储中读取就可以做到这一点,但这并不意味着订阅未来的更改。

【讨论】:

以上是关于在 redux 状态更改后,React 导航不会更改导航屏幕的主要内容,如果未能解决你的问题,请参考以下文章

Redux状态更改后,简单的React组件不会更新

React s-s-r路由器-导航到子路由时的redux状态丢失

react-router-redux 5:维护导航中的商店状态历史记录

组件不会重新渲染,但 redux 状态已通过反应钩子更改

React,redux 组件不会在路由更改时更新

在 react redux 中全局状态更改后调度新动作