简体   繁体   中英

State set with useState not passing down through props

I have a functional component that sets state with an onSubmit function. The problem is that the setState function won't update the user state in time for it to be passed down to the user prop before the authenticated state is changed to true. Here is the basic gist of my component:

import React, { useState } from 'react';
import axios from 'axios';
import { LoginForm } from './LoginForm';
import { LoggedIn } from './LoggedIn';

const { login } = require('../utils/login');
const { getUser } = require('../utils/getUser');

export const Splash = () => {
    const [user, setUser] = useState(null);
    const [authenticated, setAuthenticated] = useState(false);
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');

    const _handleEmail = (e) => {
        setEmail(e.target.value);
    };

    const _handlePass = (e) => {
        setPassword(e.target.value);
    };

    const _handleSubmit = async (e) => {
        e.preventDefault();
        const token = await login(email, password);
        const fetchUser = await getUser(token);
        setAuthenticated(true);
        setUser(fetchUser);
        console.log(fetchUser) <---- logs user fine
        console.log(user) <----- logs empty object
    };

    const _handleLogout = async (e) => {
        const logout = await 
             axios.get('http://localhost:5000/api/v1/auth/logout');
        console.log(
            `Logout status: ${logout.status}, Success: ${logout.data.success}`
        );
        setAuthenticated(false);
        setUser({});
    };

    return (
        <div>
            {!authenticated ? (
                <LoginForm
                    handleEmail={_handleEmail}
                    handlePass={_handlePass}
                    handleSubmit={_handleSubmit}
                />
            ) : (
                <LoggedIn
                    user={user}
                    authenticated={authenticated}
                    logout={_handleLogout}
                />
            )}
        </div>
    );
};

For whatever reason, my setAuthenticated function will change the authenticated state from false to true, hence triggering the render of <LoggedIn /> , but my user state will not update in time to be passed down to <LoggedIn /> through the user prop. I suspect the authenticated prop I send down isn't updated either. Thoughts?

Thanks!

EDIT: Here's my <LoggedIn /> component and it's <LoggedInNav /> component.

/// LoggedIn.js

export const LoggedIn = ({ logout, user, authenticated }) => {
    LoggedIn.propTypes = {
        logout: PropTypes.func.isRequired,
        user: PropTypes.object.isRequired,
        authenticated: PropTypes.bool.isRequired
    };
    const [loggedInUser, setUser] = useState({});
    const [loggedInAuth, setAuthenticated] = useState(false);

    useEffect(() => {
        try {
            if (user) {
                setAuthenticated(true);
                setUser(user);
            }
        } catch (err) {
            console.log(err);
        }
    }, []);

    return (
        <div>
            {!authenticated ? (
                <Spinner animation='border' variant='primary' />
            ) : (
                <LoggedinNav navUser={user} logoutButton={logout} />
            )}
        </div>
    );
};

// LoggedInNav.js

export const LoggedinNav = ({ navUser, logoutButton }) => {
    LoggedinNav.propTypes = {
        navUser: PropTypes.object.isRequired,
        logoutButton: PropTypes.func.isRequired
    };

    return (
        <div>
            <Navbar bg='light' variant='light'>
                <Navbar.Brand href='#home'>
                    <img
                        src={logo}
                        width='120'
                        height='auto'
                        className='d-inline-block align-top ml-3'
                        alt='React Bootstrap logo'
                    />
                </Navbar.Brand>
                <Nav className='mr-auto'>
                    <Nav.Link href='#feedback'>Submit Feedback</Nav.Link>
                </Nav>
                <Navbar.Collapse className='justify-content-end mr-4'>
                    <Navbar.Text>
                        Signed in as: {navUser.name.first} {navUser.name.last} - Role:{' '}
                        {navUser.role}
                    </Navbar.Text>
                </Navbar.Collapse>
                <Button onClick={logoutButton} variant='outline-primary mr-4'>
                    Logout
                </Button>
            </Navbar>
        </div>
    );
};

According to React Docs , useState, same as setState, triggers a new render.

Next render, the new value will be in user .
Because you are in an async method, youre out of the loop, and cannot see new state value in the next line.

See codesandbox poc :
In it the async function awaits 2 seconds and then set state.
You can see in the console that the render is triggered after the set state, the render sees the new state, but the async method prints the old one.

useState, same as setState, are syncronous, meaning they will directly trigger a render.

For async work, you can use useEffect

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM