React useContext 不会更新子组件,但会在页面刷新时起作用

React useContext doesn't update the child component, but works on page refresh

我有一个带有子菜单的导航栏组件。登录后,导航栏的子菜单应该改变。 我使用了钩子 useContext,但它不会在用户登录时刷新导航栏组件。当我刷新页面时它工作正常。 我的代码问题在哪里?

应用组件

import React, { useState, useEffect } from "react";
import logo from "./assets/logoBusca.png";
import "./App.css";
import { Route } from "wouter";
import Home from "./components/Home";
import Login from "./components/Login";
import Post from "./components/Post";
import NavbarUser from "./components/NavbarUser";
import { AuthContext } from "./context/AuthContext";
import logic from "../src/logic";

function App() {
  const [user, setUser] = useState(false);

  useEffect(() => {
    (async () => {
      const loggedIn = await logic.isUserLoggedIn;
      if (loggedIn) setUser(true);
    })();
  }, [user]);

  return (
    <AuthContext.Provider value={user}>
      <div className="App">
        <header className="App-header">
          <div className="images">
            <div className="logo">
              <a href="/">
                <img src={logo} alt="logo" />
              </a>
            </div>
            <div className="user_flags">
              <NavbarUser />
            </div>
          </div>
        </header>
        <Route path="/">
          <Home />
        </Route>
        <Route path="/login">
          <Login />
        </Route>
        <Route path="/nuevabusqueda">
          <Post />
        </Route>
      </div>
    </AuthContext.Provider>
  );
}

export default App;

导航栏组件

import React, { useContext } from "react";
import userIcon from "../../assets/userIcon.png";
import { AuthContext } from "../../context/AuthContext";

export default function NavbarUser() {
  const isAuthenticated = useContext(AuthContext);

  return (
    <>
      {!isAuthenticated ? (
        <div className="navbar-item has-dropdown is-hoverable">
          <img src={userIcon} alt="user" />
          <div className="navbar-dropdown">
            <a href="/login" className="navbar-item" id="item_login">
              Login
            </a>
            <hr className="navbar-divider" />
            <a href="/registro" className="navbar-item" id="item_register">
              Registro
            </a>
          </div>
        </div>
      ) : (
        <div className="navbar-item has-dropdown is-hoverable">
          <img src={userIcon} alt="user" />
          <div className="navbar-dropdown">
            <a href="/datos" className="navbar-item" id="item_login">
              Perfil
            </a>
            <hr className="navbar-divider" />
            <a href="/user" className="navbar-item" id="item_register">
              Logout
            </a>
          </div>
        </div>
      )}
    </>
  );
}

上下文组件

import { createContext } from "react";

export const AuthContext = createContext();

逻辑组件

import buscasosApi from "../data";

const logic = {
  set userToken(token) {
    sessionStorage.userToken = token;
  },

  get userToken() {
    if (sessionStorage.userToken === null) return null;
    if (sessionStorage.userToken === undefined) return undefined;
    return sessionStorage.userToken;
  },

  get isUserLoggedIn() {
    return this.userToken;
  },

  loginUser(email, password) {
    return (async () => {
      try {
        const { token } = await buscasosApi.authenticateUser(email, password);
        this.userToken = token;
      } catch (error) {
        throw new Error(error.message);
      }
    })();
  },
};
export default logic;

我认为 Login 组件没有调用 setUser(true)

这是它可能如何工作的示例。

const { useState, useEffect, createContext, useContext } = React;

const AuthContext = createContext();

const Login = () => {
  const [isAuthenticated, setAuth] = useContext(AuthContext);
  const onClick = () => setAuth(true);
  return <button disabled={isAuthenticated} onClick={onClick}>Login</button>
}

const App = () => {
  const [isAuthenticated] = useContext(AuthContext);
  
  return <div>
    <Login/>
    <button disabled={!isAuthenticated}>{isAuthenticated ? "Authenticated" : "Not Authenticated"}</button>
  </div>;
}

const AuthProvider = ({children}) => {
  const [isAuthenticated, setAuth] = useState(false);
  
  return <AuthContext.Provider value={[isAuthenticated, setAuth]}>
    {children}
  </AuthContext.Provider>;
}

ReactDOM.render(
    <AuthProvider>
      <App />
    </AuthProvider>,
    document.getElementById('root')
  );
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>

在我看来你没有更新状态。最初你的状态是 false。第一次渲染后,你的效果被触发并且状态再次设置为 false。在那之后你的效果不再 运行 了,因为它取决于它应该改变的状态。没有状态变化 - 没有效果 - 没有状态再次变化。另外,如果状态发生变化,它会触发效果,但你不需要这个。

您的目标是构建一个系统:

  1. 检查当前负载状态
  2. 更改时更新(或重新检查)状态

为此,您需要一些方法来异步发送来自 logic 的消息以做出反应。您可以通过某种订阅来完成此操作,例如:

const logic = {
  set userToken(token) {
    sessionStorage.userToken = token;
  },

  get userToken() {
    if (sessionStorage.userToken === null) return null;
    if (sessionStorage.userToken === undefined) return undefined;
    return sessionStorage.userToken;
  },

  get isUserLoggedIn() {
    return this.userToken;
  },

  loginUser(email, password) {
    return (async () => {
      try {
        const { token } = await buscasosApi.authenticateUser(email, password);
        this.userToken = token;
        this.notify(true);
      } catch (error) {
        throw new Error(error.message);
      }
    })();
  },
  subscribers: new Set(),
  subscribe(fn) {
    this.subscribers.add(fn);
    return () => {
      this.subscribers.remove(fn);
    };
  },
  notify(status) {
    this.subscribers.forEach((fn) => fn(status));
  },
};

function useAuthStatus() {
  let [state, setState] = useState("checking");
  let setStatus = useCallback(
    (status) => setState(status ? "authenticated" : "not_authenticated"),
    [setState]
  );
  useEffect(function () {
    return logic.subscribe(setStatus);
  }, []);

  useEffect(function () {
    setStatus(logic.isUserLoggedIn);
  }, []);

  return state;
}

注意,现在有三种可能的状态 - 'checking'、'authenticated' 和 'not_authenticated'。它更详细,可以防止一些错误。例如,如果您希望在用户未通过身份验证时将用户重定向到登录页面。