【问题标题】:Unable to get updated state after dispatching an async call from reducer从 reducer 分派异步调用后无法获取更新状态
【发布时间】:2020-03-28 22:22:16
【问题描述】:

在向异步调用分派操作后,我正在尝试获取用户的更新状态并将其存储在用户的上下文中。我确实在 reducer 函数中获取了数据,但是返回到我的组件的状态没有改变。

我将异步调用放在我的 reducer 函数中,并在 App.js 中调用 useReducer。之后,我将状态和从 App.js 分派给它可能需要的任何组件。

我可能做错了什么,因为我还是 React 的新手。这是我的尝试:

// UserContext.js
import React from "react";

// create user context
const UserContext = React.createContext({
  status: "",
  message: "",
  username: "",
  password: "",
  users: []
});

export default UserContext;
// UserReducer.js
import axios from "axios";

// import action
import { AUTH_USER } from "./UserActionTypes";

// import endpoint
import { AUTH } from "../../constants";

// authenticate user
const authenticateUser = async (payload, state) => {
  // API call
  const response = await axios.post(`${AUTH}?type=${payload.type}`, {
    username: payload.username,
    password: payload.password
  });

  const data = response.data;

  console.log(`API Data: ${JSON.stringify(data)}`); // data shown

  return {
    ...state,
    status: data.status,
    message: data.message,
    users: data.results
  };
};

const UserReducer = async (state, action) => {
  switch (action.type) {
    case AUTH_USER:
      const newState = await authenticateUser(action.payload, state);
      console.log(`New State: ${JSON.stringify(newState)}`); // new state shown
      return newState;
    default:
      return state;
  }
};

export default UserReducer;
// App.js
import UserContext from "./context/User/UserContext";
import UserReducer from "./context/User/UserReducer";

export default function App() {
  // initial state
  const initialState = {
    status: "",
    message: "",
    username: "",
    password: "",
    users: []
  };

  // useReducer
  const [state, dispatch] = useReducer(UserReducer, initialState);

  return (
    <UserContext.Provider value={{ state, dispatch }}>
      <Router history={history}>
        <Routes />
      </Router>
    </UserContext.Provider>
  );
}
// Login.js
import React, { useState, useContext } from "react";
import UserContext from "../../context/User/UserContext";
import { AUTH_USER } from "../../context/User/UserActionTypes";

const Login = () => {
  const { state, dispatch } = useContext(UserContext);

  // initail form data
  let initialForm = {
    username: "",
    password: "",
    usernameError: null,
    passwordError: null
  };

  // useState
  const [form, setForm] = useState(initialForm);

  // submit form
  const handleSubmit = e => {
    e.preventDefault();

    if (!form.username) {
      setForm({ ...form, usernameError: "الرجاء إدخال اسم المستخدم" });
      return;
    }

    if (!form.password) {
      setForm({ ...form, passwordError: "الرجاء إدخال كلمة المرور" });
      return;
    }

    // dispatch auth action
    dispatch({
      type: AUTH_USER,
      payload: {
        username: form.username,
        password: form.password,
        type: "db"
      }
    });

    console.log(`Context State: ${JSON.stringify(state)}`); // here (state) is still empty
  };

  return (
    <DirectionProvider direction={DIRECTIONS.RTL}>
      <div className="centered">
        <form onSubmit={e => handleSubmit(e)}>
          <Pane>
            <TextInputField
              name="username"
              placeholder="اسم المستخدم"
              value={form.username || ""}
              onChange={e => {
                setForm({ ...form, username: e.target.value });
              }}
              validationMessage={form.usernameError}
            />
            <TextInputField
              type="password"
              name="password"
              placeholder="كلمة المرور"
              value={form.password || ""}
              onChange={e => {
                setForm({ ...form, password: e.target.value });
              }}
              validationMessage={form.passwordError}
            />
            <Button type="submit">
              تسجيل الدخول
            </Button>
          </Pane>
        </form>
      </div>
    </DirectionProvider>
  );
};

export default Login;

我正在使用: 反应:“^16.12.0”, 反应路由器:“^5.1.2”, react-router-dom: "^5.1.2",

【问题讨论】:

    标签: reactjs react-hooks react-context


    【解决方案1】:

    reducer 不应该是异步的并且有任何副作用,例如进行 api 调用。他们所做的只是将更改应用于状态以产生新状态。它们应该是纯函数。

    因此,在您的情况下,您需要在 reducer 之外对用户进行身份验证,并将该调用的结果传递给 reducer 以计算新状态。您很可能需要从 authenticateUser 分派更新状态的操作,这需要移动到 Login 组件中:

    // submit form
      const handleSubmit = async e => {
        e.preventDefault();
    
        if (!form.username) {
          setForm({ ...form, usernameError: "الرجاء إدخال اسم المستخدم" });
          return;
        }
    
        if (!form.password) {
          setForm({ ...form, passwordError: "الرجاء إدخال كلمة المرور" });
          return;
        }
    
        const newState = await authenticateUser({
            username: form.username,
            password: form.password,
            type: "db"
          });
    
        // dispatch auth action
        dispatch({
          type: AUTH_USER,
          payload: newState
        });
      };
    
    const UserReducer = (state, action) => {
      switch (action.type) {
        case AUTH_USER:
          return {...state, ...action.payload};
        default:
          return state;
      }
    };
    

    还请记住,您在这里分派异步操作,因此如果您在分派后立即 console.log 状态,您仍会看到该状态的旧值。要检查新值,您可以使用 useEffect 挂钩,或将 console.log 移到 handleSubmit 之外:

    useEffect(() => {
      console.log(state) // get updated value of the state
    }, [state])
    

    【讨论】:

    • 我尝试了您的解决方案,但它不起作用。此外,return {...state, action.payload}; 中存在语法错误
    • 对,应该是{...state, ...action.payload}。我会更新答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-22
    • 1970-01-01
    • 2019-11-02
    • 2019-10-24
    • 2021-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多