Value of react context is not updated

Viewed 779

I have a client implemented in react typescript, which needs to work with user data. Therefore, I've created an AppContext.

//appState.ts

export interface UserStateProperties {
    authenticated: boolean,
    user: GetUserResponse | undefined,
    notificationManager: NotificationManager | undefined
}

export interface AppContextProperties {
    userState: UserStateProperties,
    setUserState: Dispatch<SetStateAction<UserStateProperties>>
}

const AppContext = React.createContext<AppContextProperties>({ 
    userState: {
        authenticated: false,
        user: undefined,                   // userData like name, level, ...
        notificationManager: undefined    // contains socket to receive notifications
    }, 
    setUserState: () => {}
});

export default AppContext;

In my App component, I instantiate a state for the user and passed it as value to an AppContext.Provider.

// App.tsx

function App() {

  const [userState, setUserState] = useState<UserStateProperties>({
    authenticated: false,
    user: undefined,                         
    notificationManager: undefined
  });

  return (
    <Suspense fallback={'Die Seite lädt...'}>
      <AppContext.Provider value ={{userState, setUserState}}>
        <Router history={createBrowserHistory()}>
          <Switch>
            <Route path='/' exact component={ Home }/>
            <Route path='/auth/forgot' exact component = { Forgot } />
            <Route path='/auth/:type' exact component={ Auth }/>
             // A lot more components
            <Route component={ ErrorPage }/>
          </Switch>
        </Router>
      </AppContext.Provider>
    </Suspense>
  );
}

Each of my components (e.g Home)

// Home.tsx
...
return(
        <Frame current='/'>
            <section className='home-landingPage'>
            ...
        </Frame>
)

are wrapped in a Frame component

// Frame.tsx

interface FrameProperties {
    children: React.ReactNode | React.ReactNode[],
    current?: string
}

export default function Frame(props: FrameProperties) {
    return (
        <div className='frame-container'>
            <NavigationBar current={ props.current } />
                { props.children }
            <Footer/>
        </div>
    )
}

which adds a NavigationBar to the component. In this NavigationBar, I am rendering things like signin/signup button (in case authenticated == false) or signout button, profile picture, level progress (in case authenticated == true). To ensure that the navigation bar displays the correct information, I use an effect hook, which updates the userStatus.

//Navigation.tsx

import AppContext from '../../../context/appState';
...

export default function NavigationBar(props: NavigationBarProps) {

    const {userState, setUserState} = useContext(AppContext)
    const updateUser = async () => {
        fetchGetOwnUser().then(response => {
            if(response.status === 200) {
               setUserState({...userState, user: response.data});    // update user
            }
        }).catch(err => {
            console.error(err);
        });
        console.log("USERSTATE AFTTER: ");
        console.log(userState);
    }

    const updateAuthenticationStatus = async () => {
        const accessToken = localStorage.getItem('accessToken');
        if(accessToken) {
            fetchVerifyToken({token: accessToken})
            .then(response => {
                if(response.status == 200){
                    const userId = getTokenPayload(accessToken).sub;
                    setUserState({authenticated: true, user: userState.user, notificationManager: userState.notificationManager || new NotificationManager(userId)}); //update authentication status of user
                }
            })
            .catch(err => {
                 console.error(err);
            });
      console.log("USERSTATE AFTER: ");
      console.log(userState);
   }

   useEffect(() => {
        console.log("USERSTATE BEFORE: ");
        console.log(userState);
        if(userState.authenticated){
            updateUser();
        }else{
            updateAuthenticationStatus();
        }    
    }, []);
}

However, although updateAuthenticationStatus and updateUser are executed succesfully, the userState object does not change. The console shows the following output.

USERSTATE BEFORE: Object { authenticated: false, user: undefined, notificationManager: undefined }

USERSTATE AFTTER: Object { authenticated: false, user: undefined, notificationManager: undefined }

Thanks for any help in advance!

1 Answers

Your code looks fine, you just have written your log statements in spots where they are useless. fetchVerifyToken is asynchronous, so a log statement on the next line will run before fetchVerifyToken is complete, and even if you awaited the promise, userState is a local const and is never going to change.

What you really care about is that the component rerenders with the new value, so put your console.log in the body of the component to verify that it rerenders. For example:

export default function NavigationBar(props: NavigationBarProps) {
    const {userState, setUserState} = useContext(AppContext)
    console.log('rendered with', userState);
    // ...

Related