反应:无法使用 useContext 钩子在 app.js 中设置上下文状态

Posted

技术标签:

【中文标题】反应:无法使用 useContext 钩子在 app.js 中设置上下文状态【英文标题】:React : Unable to set context state in app.js using useContext hook 【发布时间】:2020-10-20 10:18:13 【问题描述】:

我无法在我的 app.js 中设置上下文状态,我在其中得到了空值,但可以在子组件中访问它。

我想在用户访问页面时在 app.js 中设置上下文状态,以便我可以在整个应用程序中使用它,例如根据用户是否登录显示不同的标题

请求的沙盒 URL -> https://codesandbox.io/s/quizzical-snyder-2qghj?file=/src/App.js

我关注https://upmostly.com/tutorials/how-to-use-the-usecontext-hook-in-react

app.js

// import installed dependencies
import React,  useEffect, useContext  from 'react';
import  BrowserRouter as Router, Switch, Route  from 'react-router-dom';

// import custom contexts
import  AuthContext, AuthContextProvider  from './contexts/auth/AuthContext';

// import pages
import Homepage from './pages/homepage/Homepage';

// import components
import Footer from './components/footer/Footer';
import Header from './components/header/Header';

export default function App() 
    const [authState, setAuthState] = useContext(AuthContext);

    useEffect(() => 
        console.log(authState); // prints **
        console.log(setAuthState); // prints *() => *
        const token = localStorage.getItem('token');
        const tokenIsExpired = parseInt(localStorage.getItem('tokenIsExpired'));

        if (!tokenIsExpired && token.length) 
            setAuthState(
                userIsLoggedin: true,
                fName: 'test fname',
                lName: 'test lname',
                userName: 'testname'
            );
         else 
            setAuthState(
                userIsLoggedin: false,
                fName: '',
                lName: '',
                userName: ''
            );
        

        if (tokenIsExpired) 
            localStorage.setItem('token', '');
        
    , [authState, setAuthState]);

    return (
        <Router>
            <AuthContextProvider value=[authState, setAuthState]>
                <div className='App'>
                    <Header />
                    <Switch>
                        <Route exact path='/'>
                            <Homepage />
                        </Route>
                    </Switch>
                    <Footer />
                </div>
            </AuthContextProvider>
        </Router>
    );

AuthContext.js

import React,  useState, createContext  from 'react';

const AuthContext = createContext([, () => ]);

const AuthContextProvider = (props) => 
    const [authState, setAuthState] = useState(
        userIsLoggedin: false,
        fName: '',
        lName: '',
        userName: ''
    );
    return (
        <AuthContext.Provider value=[authState, setAuthState]>
            props.children
        </AuthContext.Provider>
    );
;

export  AuthContext, AuthContextProvider ;

UseAuthCONtext.js

import  useContext  from 'react';
import  AuthContext  from './AuthContext';

const useAuthContext = () => 
    const [authState, setAuthState] = useContext(AuthContext);

    const login = (loginDetails) => 
        setAuthState(
            userIsLoggedin: true,
            fName: 'test fname',
            lName: 'test lname',
            userName: 'testname'
        );
    ;

    const logout = () => 
        setAuthState(
            userIsLoggedin: false,
            fName: '',
            lName: '',
            userName: ''
        );
    ;

    return  login, logout ;
;

export default useAuthContext;

Header.js

// import installed dependencies
import React,  useContext, useEffect  from 'react';

// import components
import LoggedOutHeader from './logged-out-header/LoggedOutHeader';
import LoggedInHeader from './logged-in-header/LoggedInHeader';

// import custom contexts
import  AuthContext  from '../../contexts/auth/AuthContext';

const Header = () => 
    const [authState, setAuthState] = useContext(AuthContext);
    console.log(authState);  //prints *userIsLoggedin: false, fName: "", lName: "", userName: ""*
    console.log(setAuthState); //prints *ƒ dispatchAction(fiber, queue, action) ...*
    const header = authState.isUserLoggedIn ? (
        <LoggedInHeader />
    ) : (
        <LoggedOutHeader />
    );

    return header;
;

export default Header;

【问题讨论】:

规则101永远不要在useEffect的依赖数组中添加setter函数 请创建沙盒并分享 【参考方案1】:

您可以在index.js 中使用上下文提供程序。

ReactDOM.render(
    <AuthContextProvider>
        <App />
    </AuthContextProvider>, 
    document.getElementById('root')
)

【讨论】:

【参考方案2】:

您将value 传递给AuthContextProvider,这似乎是您想要使用的value,但您没有使用它。

// value not used inside `AuthContextProvider`
<AuthContextProvider value=[authState, setAuthState]>

应该是:

const AuthContextProvider = (props) => 
  return (
    <AuthContext.Provider value=props.value>
      props.children
    </AuthContext.Provider>
  );
;

【讨论】:

在 app.js 中仍然得到相同的空值 你有可生产的例子吗? How to create a Minimal, Reproducible Example, codesandbox 添加了有问题的沙盒【参考方案3】:

您正在使用 App 组件中的上下文,该上下文未包含在 AuthContextProvider 中。在这种情况下,App 组件中的 useContext 调用不会返回提供给 AuthContextProvider 的值,而是会返回提供给 createContext 调用的“默认”值。

您需要将 App 组件中的这些逻辑推迟到 AuthContextProvider 中的子组件。

见createContext api的注释: defaultValue 参数仅在组件树中没有匹配的 Provider 时使用。这有助于在不包装组件的情况下单独测试组件。注意:将 undefined 作为 Provider 值传递不会导致消费组件使用 defaultValue。

【讨论】:

以上是关于反应:无法使用 useContext 钩子在 app.js 中设置上下文状态的主要内容,如果未能解决你的问题,请参考以下文章

如何将 React 钩子(useContext、useEffect)与 Apollo 反应钩子(useQuery)结合起来

使用React useContext hook进行分派调用后,反应状态不会更新

如何测试组件中的react useContext useReducer调度

如何用反应钩子完全替换redux?

React TS useContext useReducer 钩子

为啥带有 useContext 触发器的自定义路由 HOC 会重新渲染?