1

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!

sethgypt
  • 23
  • 7

1 Answers1

0

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);
    // ...

Nicholas Tower
  • 72,740
  • 7
  • 86
  • 98
  • thanks for your advice, indeed the userState is updated. However, If I reload the page (e.g. the Home page), then the userState has its default values (false, undefined, undefined) again. Is it possible that the updated userState is still present when i reload the page? – sethgypt Oct 09 '21 at 11:29
  • You could add some code into app.jsx to save data to local storage or session storage, and read from storage to set the initial state when the component mounts – Nicholas Tower Oct 09 '21 at 11:39
  • Alright, I've came across this solution before. Thank you a lot for your help and have a nice day! – sethgypt Oct 09 '21 at 11:43