上下文和 Reducer 不返回状态

Context & Reducer not returning State

有人可以告诉我为什么状态没有从 reducer 更新吗?当 state 从 reducer returned 时,useEffect(()=>{}) 没有被触发。我已验证传递给 return 的信息是否正确,但从 LoginScreen 中看不到任何内容。

上下文脚本


import React, { createContext, useReducer } from "react";
import userReducer from "./UserReducer";

export const UserContext = createContext();

const initialState = {
  userData: [],
  isLoggedIn: false,
  isAdmin: false,
  isEmployee: false,
  errorMessage: [{ success: false, statusCode: 0, error: null }],
};

const UserContextProvider = ({ children }) => {
  const [state, dispatch] = useReducer(userReducer, initialState);

  const registerUser = (user) =>
    dispatch({ type: "REGISTER_USER", payload: user });

  const loginUser = (user) => dispatch({ type: "LOGIN_USER", payload: user });

  const deleteUser = (user) => dispatch({ type: "DELETE_USER", payload: user });

  const updateUser = (user) => dispatch({ type: "UPDATE_USER", payload: user });

  const contextValues = {
    ...state,
    registerUser,
    loginUser,
    deleteUser,
    updateUser,
  };

  return (
    <UserContext.Provider value={contextValues}>
      {children}
    </UserContext.Provider>
  );
};

export default UserContextProvider;

Reducer 脚本

import axios from "axios";
axios.defaults.withCredentials = true;

const userReducer = (state = {}, action) => {
  let config = {
    header: {
      "Content-Type": "application/json",
    },
  };
  switch (action.type) {
    case "REGISTER_USER":
      break;

    case "LOGIN_USER":
      console.log(state);

      const email = action.payload.email;
      const password = action.payload.password;

      axios
        .post("/api/user/login", { email, password }, config)
        .then((response) => {
          if (response.data.success) {
            // localStorage.setItem("authToken", response.data.authToken);
            state.userData = response.data.user;
            state.isLoggedIn = true;
            if (response.data.user.role === 9) {
              state.isAdmin = true;
              state.isEmployee = true;
            } else {
              state.isAdmin = false;
              state.isEmployee = false;
            }
          }
        })
        .catch((error) => {
          state.errorMessage = {
            success: error.response.data.success,
            statusCode: error.response.status,
            message: error.response.data.error,
          };
        });

      return {
        ...state,
        userData: [state.userData],
        isLoggedIn: state.isLoggedIn,
        isAdmin: state.isAdmin,
        isEmployee: state.isEmployee,
        errorMessage: [state.errorMessage],
      };

    default:
      return state;
  }
};

export default userReducer;

登录表单

import { useState, useEffect, useContext } from "react";
import { Link } from "react-router-dom";
import {
  Button,
  Form,
  Grid,
  Message,
  Segment,
  Image,
  Container,
} from "semantic-ui-react";

//Custom Imports
import "./LoginScreen.css";
import Logo from "../../../img/logo.png";

//Context
import { UserContext } from "../../context/UserContext";

const LoginScreen = ({ history }) => {
  const { userData, loginUser, isLoggedIn, errorMessage, clearErrorMessage } =
    useContext(UserContext);
  const [user, setUser] = useState({ email: "", password: "" });
  const [error, setError] = useState("");

  useEffect(() => {
    console.log(errorMessage);
    if (localStorage.getItem("authToken")) {
      history.push("/dashboard");
    }
  }, [history]);

  useEffect(() => {
    if (isLoggedIn) {
      console.log(userData);
      console.log("User is Logged in");
      // history.push("/");
    }

    if (!errorMessage.success && errorMessage.error != null) {
      console.log(errorMessage);
      setError(errorMessage.message);
      setTimeout(() => {
        setError("");
      }, 5000);
    }
  }, [userData, errorMessage, isLoggedIn]);

  return (
    <Container className="login-container">
      <Grid
        textAlign="center"
        style={{ height: "100vh" }}
        verticalAlign="middle"
      >
        <Grid.Column style={{ maxWidth: 450 }}>
          <Image src={Logo} className="login-logo" />
          <Form size="large" onSubmit={() => loginUser(user)}>
            <Segment stacked>
              <Form.Input
                fluid
                icon="user"
                iconPosition="left"
                placeholder="Email Address"
                value={user.email}
                onChange={(e) => setUser({ ...user, email: e.target.value })}
              />
              <Form.Input
                fluid
                icon="lock"
                iconPosition="left"
                placeholder="Password"
                value={user.password}
                type="password"
                onChange={(e) => setUser({ ...user, password: e.target.value })}
              />
              {error && <span>{error}</span>}
              <Button color="blue" fluid size="large" type="submit">
                Login
              </Button>
            </Segment>
          </Form>
          <Message>
            Don't have an account? <Link to="/register">Sign Up</Link>
          </Message>
        </Grid.Column>
      </Grid>
    </Container>
  );
};

export default LoginScreen;

像这样重构您的登录功能

const loginUser({ email, password }) => {
  let config = {
    header: {
      "Content-Type": "application/json",
    },
  };

  axios
        .post("/api/user/login", { email, password }, config)
        .then((response) => {
          if (response.data.success) {
            dispatch({ type: 'LOGIN_SUCCESS', payload: response.data });
          }
        })
        .catch((error) => {
          dispatch({ type: 'LOGIN_FAILED', payload: error });
        });
}

然后是你的减速器


...

switch(action.type) {
  ...
  case 'LOGIN_SUCCESS':
    // return here a new object
    // do not mutate the state (state.something = something) is not allowed

  ...

  case 'LOGIN_FAILED':
    // handle error
}

先决条件 Reducer 概念

Redux 和 useReducer 使用 reducer 就像 (previousState, action) => newState

reducer 应该是 'pure' 函数,如 this document 中那样。承诺,api 不应在减速器内部使用调用。

问题:

因为你在reducer里面调用了api/promise。 reducer 函数 returns promise 完成前的值。所以当 promise 完成时,什么也没有发生。

// A will be return before B, C are going to call
case "LOGIN_USER":
  promiseFn()
    .then(/* B */ ...)
    .catch(/* C */ ...)

  // A
  return {
    ...
  }

解决方案:

the non-pure calls与减速机分开。并将它们放在其他代码块中(如内部挂钩、事件处理程序...)。