12

I'm new to React Hooks and using react 16.13.1.

I'm going to implement Auth component which enables to handle logging in.

But it does not seem update the state currentUser properly, even though setCurrentUser is called with response Object.

What is wrong with this code?

import React, { useState, useEffect } from "react";
import { Route, Redirect } from "react-router-dom";
import { checkLoggedIn } from "utils/Api";

export const Auth = (props) => {
  const [currentUser, setCurrentUser] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  console.log(currentUser);

  useEffect(() => {
    const f = async () => {
      setIsLoading(true);
      console.log(isLoading);
      const res = await checkLoggedIn();
      if (!!res) { // ==> true
        setCurrentUser(res);
        console.log(currentUser); // ==> null!
      }
      setIsLoading(false);
    };
    f();
  });

  if (isLoading) {
    return <div>loading</div>;
  }

  console.log(currentUser); // ==> null!
  return !!currentUser ? (
    <Route children={props.children} />
  ) : (
    <Redirect to={"/login"} />
  );
};
Taichi
  • 2,297
  • 6
  • 25
  • 47

4 Answers4

14

setCurrentUser updates the state asynchronously, so you can't use currentUser immediately afterwards.

However, you can use another useEffect to know when the state was changed:

useEffect(() => {
  // currentUser changed
}, [currentUser])

I also noticed that you are not passing an empty array to the useEffect you already have, so it will trigger everytime the component is updated. If you need to execute your useEffect only once you have to pass an empty array as a second argument, like this:

  useEffect(() => {
    const f = async () => {
      // ...
    };
    f();
  }, []);
germanescobar
  • 782
  • 5
  • 9
4

It could be a problem with calling setter of state (setCurrentUser) because of asynchronous nature of setting state in React. I haven't found any clear answer is it async as setState or sync when I was working with React. In my opinion, the best solution in such complicated cases just to use classes. It will give you a lot of lifecycle methods that cannot be reproduced with hooks and will be more appropriate in auth component. React Hooks is not a replacement of classes in all situations and just a tool that allow you to not break existing code while you need just to do simple improvements.

Vadim Ledyaev
  • 458
  • 1
  • 6
  • 9
4

As @germanescobar already answered setCurrentUser updates state asynchronously. Hence you need to useEffect with dependency list containing only currentUser to get the latest state value.

However, for your use case you're only interested in whether the currentUser is truthy and based on that you want to redirect the user to required page. So you don't need another useEffect as you are not performing any other side-effect based on the currentUser.

In real-world scenario you want to show the loading/splash screen until you're able to decide whether the user is authenticated or not. So you should change the initial value of isLoading to true and change the state to false once your async task is done. I'll suggest you to rename the state name to something like:

const [shouldCheckLogin, setShouldCheckLogin] = useState(true).

Lastly, !!null is false whereas !!{} is true. For values which are object it's completely fine to initialize them with null.

console.log(!!null)
console.log(!!{})
im_tsm
  • 1,965
  • 17
  • 29
  • Here is a great article by Dan Abramov introducing hooks: https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889 – im_tsm Oct 04 '20 at 07:43
  • `!!` what is the use of this operator? any useful link would be appreciated. – Subrato Pattanaik Oct 04 '20 at 07:57
  • Generally this is called double negation operator. It'll just perform(negate) `!` twice. The second time it performs the negation on the result of first one. Here is a SO answer: https://stackoverflow.com/questions/10467475/double-negation-in-javascript-what-is-the-purpose – im_tsm Oct 04 '20 at 08:03
  • `!! expr` is the shorthand for `Boolean(expr)` – Ismail Mar 14 '22 at 01:18
2

Thank you @germanescobar and @Vadim for teaching me many things!

And I noticed now that the argument of useState has something to do with this problem.

changing

const [currentUser, setCurrentUser] = useState(null);

into

const [currentUser, setCurrentUser] = useState({});

seems to have solved the not-updating problem (I don't know the reason though).

Taichi
  • 2,297
  • 6
  • 25
  • 47
  • 1
    It's a good practice to have initial state as the same type of what you will assign later to it. Hard to say that this is really solves the problem. Better to stick with @germanescobar solution or to use classes, I think – Vadim Ledyaev Oct 04 '20 at 07:31
  • I used germanescobar's solution too, but I had to change initial state to {} too. – Taichi Oct 04 '20 at 07:37