Page loads before user data is saved from useEffect() to useContext() in React.js

Viewed 611

I was using react for my project. In the App.js file(the entry point) it get's the current user via an API like this.

   
function App() {

    const [user, setUser] = useState(null);
    const [isLoading, setIsLoading] = useState(true);
    const providerValue = useMemo(() => ({user, setUser}), [user, setUser])
    
    useEffect(() => { 
        // IIF b/c u can't use async on useEffect func
        (async () => {
            var headers = new Headers();
            headers.append('Content-Type', 'application/json');
            headers.append('Authorization', Cookies.get("authorization"));
            const response = await fetch('http://localhost:3000/api/users/current', {headers});
            
            const content = await response.json();
            console.log({"from app user": content});
            setUser(content);
        })();
    },[]);

    return (
        <Switch>
            <UserContext.Provider value={providerValue}>
                <Route path="/" exact
                    component={Home}/>
                <Route path="/discover"
                    component={Main}/>
                {/* auth */}
                <Route path="/auth/signup"
                    component={SignUp}/>
                <Route path="/auth/signin"
                    component={SignIn}/>
            </UserContext.Provider>
        </Switch>
    );
}

export default App;

user and setUser are then passed as a value in my provider b/c I want to access the user data from other routes. And when I access the user from the component of my app via useContext() like this...

   export default function DiscoverNavBar(props) {

    {/* useContext to get the user*/}
    const {user} = useContext(UserContext)

    console.log(user);
    const toggleHome = () =>{
        scroll.scrollToTop()
    }
    return <>
        <Navbar collapseOnSelect expand="lg"  bg="dark" variant="dark" sticky="top">
            <Link className="navbar-brand"  onClick={toggleHome}>
                <img alt=""
                    src={logo}
                    width="30"
                    height="30"
                    className="d-inline-block align-top"/>{' '}
                Focal Addis
            </Link>
            <Navbar.Toggle aria-controls="responsive-navbar-nav"/>
            <Navbar.Collapse id="responsive-navbar-nav">
                <Nav className="mr-auto">
                    <Link className="nav-link" to="/directory">Contact us</Link>
                    <LinkS className="nav-link"
                    to="auth"
                    spy={true}
                    smooth={true}
                    exact="true"
                    duration={500} style={{cursor: "pointer"}}>Get Started</LinkS>
                </Nav>
                {props.children}

                {/* here is the problem */}
                <a>{user.email}</a>
               
            </Navbar.Collapse>
        </Navbar>
    </>
}

it keeps throwing an error saying "trying to access .email of undefined". When I comment out this line it compiles successfully and even logs the user with the data.

I also want to check if a user exists before rendering login and signup pages. I am currently trying to do it like this but it also cant get the user at the right moment. It assumes user doesn't exist. here is the code for signin page

   export default function SignIn() {

    const ERR_MSSG_STYLES = {
        border: "none",
        color: "red",
        padding: "0",
        width: "100%",
        backgroundColor: "transparent"
    }

    const {user,setUser} = useContext(UserContext)
    
    const history = useHistory()
    var errRef = useRef("")

    useEffect(() => {
        if (user) {
            console.log("already registered");
        }else{
            console.log("nope");
        }
    }, [])

    return (
        <div className="container-fluid">
            <div className="row" style={{height: "100vh"}}>
                <div className="col-md-3 px-5">
                    <Formik 
                        initialValues={{
                            email: '',
                            password: '',
                        }}
                        onSubmit={
                                async (values) => {
                                    const response = await fetch('http://localhost:3000/api/users/login-influencer', {
                                    method: 'POST',
                                    headers: {'Content-Type': 'application/json'},
                                    body: JSON.stringify({
                                        email: values["email"],
                                        password: values["password"],
                                    })
                                })
                                const content = await response.json();
                                if (content["success"] == false) {
                                    errRef.current.value = content["message"]
                                }else{
                                    // change the current user in context
                                    setUser(content)
                                    history.replace("/discover")
                                    Cookies.set('authorization', content["token"], { expires: 7 });
                                }
                            }
                        }
                        >
                        {
                            formik => (
                                <div>
                                    <h1 className="my-4 font-weight-bold-display-4">Focal Addis</h1>
                                    <p>Welcome back, please log in to your account</p>
                                    <input style={ERR_MSSG_STYLES} disabled ref={errRef}/>
                                    <Form>
                                        <TextField label="Email" name="email" type="email"/>
                                        <TextField label="Password" name="password" type="password"/>
                                        <button className="btn btn-danger mt-3 shadow-none" type="submit" style={{width:"100%"}}>Sign In</button>
                                    </Form>
                                    <div className="mt-2 text-red text-center" >
                                        <p>Don't have an account yet?</p>
                                        <Link to="/auth/signup" style={{color: "red"}}>Sign up here.</Link>
                                    </div>
                                </div>
                            )
                        }
                    </Formik>
                </div>

                <div className="col-md-9" style={{backgroundColor: "grey"}}>
                </div>
            </div>
        </div>

    )
}
I am trying to check if any user exists and log a mssg for now but it is only printing "nope"
2 Answers

You just need a check beforehand, therefore the email will only render when it's ready.

<a>{user?.email}</a> 

Or maybe

 {user?.email &&
   <a>{user.email}</a>    
  }

Edit1:

You may need to wrap the entire thing in a check to see if the user exists or not. Here is a general idea:

  return (
<>
{/*Check here if the user exists or not*/}
  {user ? (
    <Navbar
      collapseOnSelect
      expand="lg"
      bg="dark"
      variant="dark"
      sticky="top"
    >
      <Link className="navbar-brand" onClick={toggleHome}>
        <img
          alt=""
          src={logo}
          width="30"
          height="30"
          className="d-inline-block align-top"
        />{" "}
        Focal Addis
      </Link>
      <Navbar.Toggle aria-controls="responsive-navbar-nav" />
      <Navbar.Collapse id="responsive-navbar-nav">
        <Nav className="mr-auto">
          <Link className="nav-link" to="/directory">
            Contact us
          </Link>
          <LinkS
            className="nav-link"
            to="auth"
            spy={true}
            smooth={true}
            exact="true"
            duration={500}
            style={{ cursor: "pointer" }}
          >
            Get Started
          </LinkS>
        </Nav>
        {props.children}
        {user?.email && <a>{user.email}</a>}
      </Navbar.Collapse>
    </Navbar>
  ) : (
    <h4>User is null</h4>
  )}
</>

);

What value of user before API call, e.g. on first render? Value is null, can't access null.email. useEffect and API call will happen only after that.

Provide default value for user

const [user, setUser] = useState({});

and check

useEffect(() => {
    if (user?.email) {
        console.log("already registered");
    }else{
        console.log("nope");
    }
}, [])
Related