Abhishek Dhanraj Shahdeo
Abhishek Dhanraj Shahdeo

Reputation: 1356

React : Unable to set context state in app.js using useContext hook

I am unable to set context state in my app.js, I get empty values in it somehow, but can access it in child component.

I want to set context state in app.js whenever user comes to page so that I can use it throughout the application, for example show different headers based on whether user is logged in or not

SandBox URL as requested -> https://codesandbox.io/s/quizzical-snyder-2qghj?file=/src/App.js

I am following 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;

Upvotes: 2

Views: 8874

Answers (3)

Eyoab
Eyoab

Reputation: 755

You could use the context provider inside index.js.

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

Upvotes: 8

sidecus
sidecus

Reputation: 752

Your are using the context in the App component, which is not wrapped within AuthContextProvider. In that case the useContext call in the App component will not return the value provided to AuthContextProvider but instead it'll return the "default" values provided to the createContext call.

You need to defer those logic in the App component to a children component within AuthContextProvider.

See note from createContext api: The defaultValue argument is only used when a component does not have a matching Provider above it in the tree. This can be helpful for testing components in isolation without wrapping them. Note: passing undefined as a Provider value does not cause consuming components to use defaultValue.

Upvotes: 1

Dennis Vash
Dennis Vash

Reputation: 53874

You are passing value to AuthContextProvider, which seems like the value you want to use, and you don't use it.

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

It should be:

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

Upvotes: 0

Related Questions