【问题标题】:Function not receiving updated redux state even on re-render即使在重新渲染时,函数也没有收到更新的 redux 状态
【发布时间】:2021-01-18 02:28:38
【问题描述】:

我目前正在使用 Redux、Redux Thunk 和 NextJS,并试图弄清楚如何在功能组件的函数中访问更新后的 redux 状态。

正如您在下面的代码中看到的那样,在 handleSubmit 函数中,我想更新 redux 状态,然后检查状态值并决定应该将用户带到哪个路由。

以前在我的旧项目中,使用 mapStateToProps 和 Class 组件,我能够在 handleSubmit 函数中访问更新后的 redux 状态,但是当使用功能组件时,两个选项(useSelector hook 或 mapStateToProps connect()) 似乎不起作用。

起初我以为组件没有重新渲染,但是当检查 useEffect() 中的状态时,我可以看到状态正在更新,并且组件能够查看更新的值。

是否有我明显遗漏的东西,或者功能组件无法实现这种方式?

loginPage.tsx

import LoginForm, { FormData } from 'components/Forms/LoginForm';
import Layout from 'components/Layout';
import { FORM_ERROR } from 'final-form';
import StatusCodes from 'lib/enums/statusCodes';
import { storeAuthToken } from 'lib/helpers/auth';
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { ApplicationState } from 'redux/store';

const LoginPage = () => {
  const router = useRouter();
  const dispatch = useDispatch();
  const { auth } = useSelector((state: ApplicationState) => ({ auth: state.auth }));

  const handleSubmit = async (values: FormData) => {
    if (values && values.username && values.password) {
      try {
        // Updates redux store
        await storeAuthToken(dispatch, values.username, values.password);
      } catch (error) {
        if (error === StatusCodes.BadRequest) {
          return { [FORM_ERROR]: 'Sorry, you have entered incorrect details. Please try again' };
        } else {
          return { [FORM_ERROR]: 'Sorry, there was an issue trying to log you in' };
        }
      }

      // Can't see updated values
      console.log('Auth: ', auth);

      if (auth.parsedJwt && auth.parsedJwt.changePassword) {
        router.push({ pathname: '/update-password' });
        return;
      }

      router.push('/dashboard');
    }
  };

  useEffect(() => {
    // Can see the updated values
    console.log('New Auth: ', auth);
  }, [auth]);

  return (
    <Layout hideProfileMenu title="Login">
      <LoginForm onSubmit={handleSubmit} />
    </Layout>
  );
};

export default LoginPage;

我已经附加了 store 和 reducer,以防我设置错误。

store.tsx

import Auth from 'lib/interfaces/auth';
import { Context, createWrapper, HYDRATE, MakeStore } from 'next-redux-wrapper';
import { AnyAction, applyMiddleware, CombinedState, combineReducers, createStore, Dispatch, Reducer } from 'redux';
import thunkMiddleware, { ThunkAction, ThunkDispatch, ThunkMiddleware } from 'redux-thunk';
import authReducer from './auth/reducer';

export interface ApplicationState {
  auth: Auth
}

const isDebug = process.env.NODE_ENV !== 'production';

const bindMiddleware = (middleware: ThunkMiddleware) => {
  if (isDebug) {
    const { composeWithDevTools } = require('redux-devtools-extension');
    return composeWithDevTools(applyMiddleware(middleware));
  }

  return applyMiddleware(middleware);
};

const combinedReducer: Reducer<ApplicationState> = combineReducers<ApplicationState>({
  auth: authReducer
});

const reducer = (state: ApplicationState, action: AnyAction) => {
  if (action.type === HYDRATE) {
    const nextState: CombinedState<ApplicationState> = {
      ...state,
      ...action.payload
    };

    return nextState;
  } else {
    return combinedReducer(state, action);
  }
};

const makeStore: MakeStore<ApplicationState> = (_context: Context) => createStore(reducer as Reducer<ApplicationState, AnyAction>, bindMiddleware(thunkMiddleware));

export const wrapper = createWrapper<ApplicationState>(makeStore, { debug: isDebug });

reducer.tsx

import Auth from 'lib/interfaces/auth';
import { Reducer } from 'redux';
import { ActionTypes, AuthAction } from './actions';

const reducer: Reducer<Auth, AuthAction> = (state: Auth = {} as Auth, action: AuthAction): Auth => {
  switch (action.type) {
    case ActionTypes.UpdateToken:
      return Object.assign({}, state, { token: action.token });
    case ActionTypes.UpdateRefreshToken:
      return Object.assign({}, state, { refreshToken: action.refreshToken });
    case ActionTypes.UpdateParsedJwt:
      return Object.assign({}, state, { parsedJwt: action.parsedJwt });
    case ActionTypes.UpdateUuid:
      return Object.assign({}, state, { uuid: action.uuid });
    default:
      return state;
  }
};

export default reducer;

【问题讨论】:

  • redux 调度程序异步运行,因此 handleSubmit 可能会在更新 auth 值之前完成。由于此函数还向路由器推送了一条新路径,我假设这将卸载登录组件,因此下面的 useEffect 将不再触发。如果删除 router.push 调用,useEffect 是否会使用新的 auth 值触发?
  • 或者,您可以让 storeAuthToken 函数返回 auth 值,以专门用于 handleSubmit 函数以及调度它。
  • @andyd_28 当我删除 router.push 调用时,useEffect 仍会以新的身份验证值触发。

标签: javascript reactjs redux react-redux redux-thunk


【解决方案1】:

我认为最简单的解决方案是从“storeAuthToken”返回 auth 对象,因为它保持错误处理并产生相同的逻辑流程,并且它修复了 cmets 中发现的异步问题。

import LoginForm, { FormData } from 'components/Forms/LoginForm';
import Layout from 'components/Layout';
import { FORM_ERROR } from 'final-form';
import StatusCodes from 'lib/enums/statusCodes';
import { storeAuthToken } from 'lib/helpers/auth';
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { ApplicationState } from 'redux/store';

const LoginPage = () => {
  const router = useRouter();
  const dispatch = useDispatch();
  
  const handleSubmit = async (values: FormData) => {
    if (values && values.username && values.password) {
      try {
        // Updates redux store
        const authResult = await storeAuthToken(dispatch, values.username, values.password);
          
        if (authResult.parsedJwt && authResult.parsedJwt.changePassword) {
          router.push({ pathname: '/update-password' });
          return;
        }
        
      } catch (error) {
        if (error === StatusCodes.BadRequest) {
          return { [FORM_ERROR]: 'Sorry, you have entered incorrect details. Please try again' };
        } else {
          return { [FORM_ERROR]: 'Sorry, there was an issue trying to log you in' };
        }
      }

      router.push('/dashboard');
    }
  };

  return (
    <Layout hideProfileMenu title="Login">
      <LoginForm onSubmit={handleSubmit} />
    </Layout>
  );
};

export default LoginPage;

【讨论】:

    猜你喜欢
    • 2020-11-05
    • 2019-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-24
    • 2019-05-31
    • 1970-01-01
    相关资源
    最近更新 更多