Cole S
Cole S

Reputation: 408

Using fetch to set state before the page renders

I'm building a guard for a React component. The guard takes an Auth0 JWT and checks our API to see if that user exists in our database. If the user does exist, we set userExists to true and redirect them to /dashboard. If the user does not exist, we call postUser and send them to /complete-profile.

The problem I'm running into is that the page is rendering before userExists is properly being set by the fetch call. In other words, if I were to run the below code, the logs would read in this order:

  1. USER DOES EXIST
  2. FETCH CALLED 1

const SomeGuard: FC<SomeGuardProps> = ({ children }) => {
  const { isAuthenticated } = useAuth();
  const isMountedRef = useIsMountedRef();

  const [userExists, setUserExists] = React.useState(true);

  const getUser = useCallback( async () => {
    try {
      var user_exists;
      fetch(`https://website.com/user`,
        {
          headers: {
            Authorization: `Bearer DFJKS54DUJ6DD.SDF2KJWEF4NKN3JKw4534.DFSKJ5HSKDJ6HF`,
          },
          method: 'GET',
        },
      ).then(response => {
        if (response.status===200) { // USER EXISTS
          console.log("FETCH CALLED 1")
          setUserExists(true);
        }
        else if (response.status===404) { // catch 404 -- USER NOT FOUND
          console.log("FETCH CALLED 2")
          setUserExists(false);
        }
        
        return response.json();
      })
    }
    catch (e) {
      console.error(e);
    }
  }, [isMountedRef]);


  const postUser = useCallback( async () => {
    // assume this works and sends a POST request to our API
    // to create a user
  )}


  useEffect(() => {
    console.log("STEP 1");
    getUser();
  }, [getUser]);



  if (isAuthenticated) {
    if (!userExists) {
      console.log("USER DOES NOT EXIST");
      postUser();
      return <Redirect to="/complete-profile" />;
    }
    else if (userExists) {
      console.log("USER DOES EXIST");
      return <Redirect to="/dashboard" />;
    }
  }

  if (!isAuthenticated) {
    console.log("TRIED TO HIT DASHBOARD WITHOUT BEING LOGGED IN");
    return <Redirect to="/login" />;
  }

  return (
    <>
      {children}
    </>
  );
};


SomeGuard.propTypes = {
  children: PropTypes.node
};

export default SomeGuard;

Because it renders the page before the fetch is called, the value that I initially set userExists to always determines which page is rendered.

Please help! What am I missing here? How can I get the fetch call to update userExists before the page is rendered?

Upvotes: 1

Views: 78

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370689

Set the initial value of userExists to something else, like null, so you can differentiate an unloaded page from a loaded and true / loaded and false page. Then, only render the children once userExists isn't null:

const [userExists, setUserExists] = React.useState(null);
if (isAuthenticated) {
    if (userExists === false) {
        console.log("USER DOES NOT EXIST");
        postUser();
        return <Redirect to="/complete-profile" />;
    }
    else if (userExists === true) {
        console.log("USER DOES EXIST");
        return <Redirect to="/dashboard" />;
    }
}
return userExists === null ? null : children;

Upvotes: 1

Related Questions