Home > Mobile >  react doesn't update the data, only after reloading page
react doesn't update the data, only after reloading page

Time:09-26

enter image description here It is my Auth Login form. I have AuthContext. Let's try log in.

enter image description here I've clicked "log in" button. And we see AuthContext "user" : underfind

enter image description here Then click reload page, and here we go, we have logged in

Why does it work like that?

Login.js

import React, {useState} from 'react'
import {useLogin} from '../hooks/useLogin'
import { useNavigate } from 'react-router-dom'

const Login = () => {
    const [email, setEmail] = useState('')
    const [password, setPassword] = useState('')
    const {login, error, isLoading} = useLogin()

    let navigate = useNavigate()

    const handleSubmit = async (e) => {
        e.preventDefault()

        await login(email, password)
        
    }
    return(
        <div className='container'>
            <h1>
                login page
            </h1>
            <form className={'d-flex flex-column'} onSubmit={handleSubmit}>
            <h3>Log in</h3>
            <label>Email:</label>
            <input
                type={'email'}
                onChange={(e) => setEmail(e.target.value)}
            />
            <label>Password</label>
            <input
                type={'password'}
                onChange={(e) => setPassword(e.target.value)}
            />
            <button disabled={isLoading} type={'submit'}>
                Log in
            </button>
            {error && <div className={'error'}>
                            {error}
                        </div>}
        </form>
        </div>
    )
}

export default Login;

useLogin.js

import { useState } from "react";
import { useAuthContext } from "./useAuthContext";


export const useLogin = () => {
    const [error, setError] = useState(null)
    const [isLoading, setIsLoading] = useState(null)
    const {dispatch} = useAuthContext()

    const login = async (email, password) => {
        setIsLoading(true)
        setError(null)

        const response = await fetch('/api/user/login', {
            method:'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({email, password})

        })

        const json = await response.json()

        if(!response.ok){
            setIsLoading(false) 
            setError(json.error)

        }
        
        if(response.ok){
            localStorage.setItem('user', JSON.stringify(json))
            dispatch({type:'LOGIN', paylaod:json})
            setIsLoading(false)
        }
    }

    return {login, isLoading, error}
}

AuthContext.js

import { createContext, useReducer, useEffect } from "react";

export const AuthContext = createContext()

export const authReducer = (state, action) => {
    switch(action.type){
        case 'LOGIN':
            return {user: action.payload}
        case 'LOGOUT':
            return {user:null}
        default:
            return state
    }
}

export const AuthContextProvider = ({children}) => {
    const [state, dispatch] = useReducer(authReducer, {
        user:null
    })

    useEffect(() => {
        const user = JSON.parse(localStorage.getItem('user'))

        if(user){
            dispatch({type:'LOGIN', payload: user})
        }
    }, [])

    console.log('AuthContext state', state)

    return(
        <AuthContext.Provider value={{...state, dispatch}}>
            {children}
        </AuthContext.Provider>
    )
}

useAuthContext.js

import { AuthContext } from "../context/AuthContext";
import { useContext } from "react";

export const useAuthContext = () => {
    const context = useContext(AuthContext)

    if(!context){
        throw Error('useAuthContext must be used inside an AuthContextProvider')
    }

    return context
}

here is Navbar.js

import React from "react";
import { useAuthContext } from "../hooks/useAuthContext";
import { useLogout } from "../hooks/useLogout";
import { Link } from "react-router-dom";


const Nav = () => {

    const {logout} = useLogout()
    const {user} = useAuthContext()
    const handleClick= () => {
        logout()
    }

    return(
        <div>

            {user && (
                    <div className="bg-dark text-light align-item-center d-flex justify-content-between m-auto container-fluid p-2">
                        <span className="m-0 p-0 d-flex align-item-center">{user.email}</span>
                        <button className="btn btn-outline-danger" onClick={handleClick}>logout</button>
                    </div>
            )}

                {!user && (
                    <div className="d-flex justify-content-between">

                        <Link to='/'>
                            <button>HOME</button>
                        </Link>
                        <Link to='/login'>
                            <button>LOGIN</button>
                        </Link>
                        <Link to='/signup'>
                            <button>SIGNUP</button>
                        </Link>
                    </div>
                )}
</div>
    )
}

export default Nav

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { AuthContextProvider } from './context/AuthContext';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <AuthContextProvider>
      <App />
    </AuthContextProvider>
  </React.StrictMode>
);

app.js

import {BrowserRouter, Routes, Route} from 'react-router-dom'
import Nav from './components/Nav';
import Home from './pages/home';
import Login from './pages/login';
import Signup from './pages/signup';

function App() {
  return (
    <div>
        <BrowserRouter>
          <Nav/>
            <Routes>
              <Route path={'/'} element={<Home/>}/>
              <Route path={'/login'} element={<Login/>}/>
              <Route path={'/signup'} element={<Signup/>}/>
            </Routes> 
        </BrowserRouter>
    </div>
  );
}

export default App;

I don't think it is server error.

CodePudding user response:

There's a spelling mistake in useLogin payload is written wrong

dispatch({type:'LOGIN', paylaod:json});

// Should be: Payload
dispatch({type:'LOGIN', payload:json});

CodePudding user response:

The root cause seems to be a typo:

dispatch({type:'LOGIN', paylaod:json})

has a typo in paylaod, so the reducer's

        case 'LOGIN':
            return {user: action.payload}

basically just sets {user: undefined}.

You should add error checking to your reducer, or better yet switch to a typed language like TypeScript so typos like these are caught at type-check time.

CodePudding user response:

Since the user is not in the component state, it will not re-render after it is set (which seems to happen after first rendering). Add a useEffect with dependency on the value that updates the state and it should be good. F.ex:

const Nav = () => {
  const {contextUser} = useAuthContext()
  const [user, setUser] = useState()
  ...
  useEffect(() => {
    if (contextUser) {
      setUser(contextUser)
    }
  }, [contextUser])
  ...
}
  • Related