Search code examples
javascriptreactjsreact-reduxlocal-storageredux-toolkit

Need to refresh to view changes in mern stack application


Upon logging in via Google OAuth, users are redirected to the home page, and their user data is stored in localStorage. The objective is to immediately show the user's name and profile on the navbar without the need to refresh the page. Presently, this information becomes visible only after a page refresh. The desired functionality is to redirect users to the home page upon login and instantly display login details on the navbar.

auth.js

import React, { useState } from 'react'
import { Avatar, Button, Paper, Grid, Typography, Container, TextField } from '@mui/material'
import LockClockOutlinedIcon from '@mui/icons-material/LockClockOutlined';
import { GoogleLogin } from '@react-oauth/google';
import { useDispatch } from 'react-redux';
import { jwtDecode } from "jwt-decode";
import { AUTH } from '../constants/actionTypes';
import { setAuthData } from '../../reducers/auth';
import useStyles from './styles'
import Input from './Input';
import Icon from './icon'
import { useNavigate } from 'react-router-dom';

export default function Auth() {
  const [isSignup,setIsSignUp] = useState(false)
  const classes = useStyles()
  const [showPassword, setShowPassword] = useState(false)
  const dispatch = useDispatch()
  const navigate = useNavigate()

  const handleSubmit = () => {

  }
  const handleChange = () => {

  }

  const handleShowPassword = () => {
    setShowPassword((prevpassowrd) => !prevpassowrd)
  }

  const switchMode = () => {
    setIsSignUp((prevflag) => !prevflag)

  }
  const googleSucess = async (res) => {
    
    
      // if profile obj exists it returns or else it throws an error
    // const token = jwtDecode(res.credential).jti
    // console.log("this is token", token)
  

    try {
          const cred = res.credential
          dispatch(setAuthData(cred))
          navigate('/')
    }catch (error){
      console.log("error dispatching", error)

    }
   

  }
  const googleFailure = (error) => {
    console.error('Sign-in failed:', error);
      
      
  }


  return (
    <Container component='main' maxWidth="xs">
      <Paper className={classes.paper} elevation={3}>
        <Avatar className={classes.avatar}>
          <LockClockOutlinedIcon />
        </Avatar>
        <Typography variant='h5'>{isSignup ? 'Sign Up' : 'Sign In'}</Typography>
        <form className={classes.form} onSubmit={handleSubmit}>
          <Grid container spacing={2}>
            {
              isSignup && (
                <>
                
                  <Input name='firstName' label='First Name' handleChange={handleChange} autoFocus half />
                  <Input name='lastName' label='Last Name' handleChange={handleChange} half />

                </>
              )
            }
            <Input name='email' label='Email Address' handleChange={handleChange} type='email' />
            <Input name='password' label='Password' handleChange={handleChange} type={showPassword ? 'text' : 'password'}
              handleShowPassword={handleShowPassword} />
            {isSignup &&
              <Input name='confirmpassword' label='Confirm Password' handleChange={handleChange} type='password' />}

          </Grid>
          
          <Button type='submit' fullWidth variant='contained' color='primary' style={{ marginTop: '15px', marginBottom : '15px' }}>
            {isSignup ? 'Sign Up' : 'Sign In'}
          </Button>
          <GoogleLogin 
          
            clientId='client id goes here'
            render={(renderProps) => (
              <Button 
              style={{ marginTop: '15px' }}
              className={classes.googleButton} 
              color='primary' 
              fullWidth 
              onClick={renderProps.onClick} 
              disabled = {renderProps.disabled}
              startIcon={<Icon />} 
              variant='contained'

              >Google {isSignup ? 'Sign Up' : 'Sign In'}</Button>
            ) }
            onSuccess={googleSucess}
            onFailure={googleFailure}
            cookiePolicy='single_host_origin'
            plugin_name = 'dont work'
            />
      
          <Grid container justify = 'center'>
            <Grid item style={{ marginTop: '15px', textAlign: 'center' }}>
                  <Button onClick={switchMode} style={{ marginTop: '15px', textAlign: 'center' }}>
                    {isSignup ? 'Already Have an account? Sign In' : 'Don\'t have account? Sign Up'}
                  </Button>
            </Grid>
          </Grid>
        </form>
      </Paper>
    </Container>
  )
}

reducers_auth.js

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { jwtDecode } from "jwt-decode";

const initialState = {
  authData: null,
};

const authSlice = createSlice({
    name: 'auth',
    initialState,
    reducers: {
        setAuthData(state, action) {
            const user_data = jwtDecode(action.payload);

            // Modify the draft state using Immer
            state.authData = user_data;
      
            // Update localStorage outside of Immer
            localStorage.setItem('profile', JSON.stringify(user_data));
        },
        logoutAuth(state, action) {
            localStorage.removeItem('profile')
        }
    }
})

export const { setAuthData, logoutAuth } = authSlice.actions;
export default authSlice.reducer;

navbar.js

import { AppBar, Typography, Toolbar, Avatar, Button, Box,Grid,Container } from "@mui/material";
import React, { useEffect, useState } from "react";
import { Link } from 'react-router-dom'
import useStyles from './styles'
import memories from '../../images/memories.png'
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { logoutAuth } from "../../reducers/auth";

const Navbar = () => {
    const classes = useStyles()
    const dispatch = useDispatch()
    const navigate = useNavigate()

    const [user,setUser] = useState(JSON.parse(localStorage.getItem('profile')))
    useEffect(() => {
        if (user) {
            setUser(JSON.parse(localStorage.getItem('profile')))
        }
        
    }, [])

    const logout = () => {
        dispatch(logoutAuth())
        navigate('/auth')
        setUser(null)
    }
  
    return (
        // <Box sx={{ flexGrow: 1 }}>
        <>
            <AppBar className={classes.appBar} position='static' color='inherit'>
                <Container maxWidth="xl" style={{ display: 'flex', flexDirection: 'row' }}>
                    <div className={classes.brandContainer}>
                        <img className={classes.image} src={memories} alt="memories" height="50" />
                        <Typography variant='h4' nowrap className={classes.heading} >Travel Blog</Typography>
                    </div>
                    <Toolbar className={classes.toolbar} style={{ marginLeft: 'auto' }}>
                        {user ? (
                            <div className={classes.profile}>
                                <Avatar className={classes.purple} alt={user.name} src={user.picture}>{user.name.charAt(0)}</Avatar>
                                <Typography className={classes.userName} variant='h6'>{user.name}</Typography>
                                <Button variant="contained" className={classes.logout} color="secondary" onClick={logout} >Logout</Button>
                            </div>
                        ) : (
                                <Button component={Link} to="/auth" variant="contained" color="primary" style={{ marginLeft: 'auto' }}>
                                    Sign In
                                </Button>
                           
                        )}
                    </Toolbar>
                </Container>
            </AppBar>
        </>
    )
}

export default Navbar

Solution

  • The Navbar component should read the user data that was stored in the Redux state, not what is stored in localStorage. Updates to localStorage don't trigger React to rerender.

    Example:

    ...
    import { useDispatch, useSelector } from "react-redux";
    ...
    
    const Navbar = () => {
      const classes = useStyles();
      const dispatch = useDispatch();
      const navigate = useNavigate();
    
      const user = useSelector(state => state.auth.authData);
    
      const logout = () => {
        dispatch(logoutAuth());
        navigate('/auth');
      };
      
      return (
        ...rendered UI with user data...
      );
    };
    
    export default Navbar;
    

    The logoutAuth action should reset the auth state. Query the initial auth state from localStorage.

    const initialState = {
      authData: JSON.parse(localStorage.getItem("profile")),
    };
    
    const authSlice = createSlice({
      name: 'auth',
      initialState,
      reducers: {
        setAuthData(state, action) {
          const user_data = jwtDecode(action.payload);
    
          state.authData = user_data;
          
          localStorage.setItem('profile', JSON.stringify(user_data));
        },
        logoutAuth(state, action) {
          localStorage.removeItem('profile');
          state.authData = null; // <-- reset to initial
        },
      },
    });