Search code examples
reactjsreact-hooksuse-effect

Component shows previous data when mount for fractions of seconds


I am developing an app named "GitHub Finder".

I am fetching the date in App component using async function and pass these function to User component as props and I call these functions in useEffect.

The problem is here, when I goto user page for second time it shows previous data which I passed in props from App component and then it shows loader and shows new data.

Here is App component code where I am fetching date from APIs and passing to User component through props.

// Get single GitHub user
  const getUser = async (username) => {
    setLoading(true);

    const res = await axios.get(
    `https://api.github.com/users/${username}?client_id=${
     process.env.REACT_APP_GITHUB_CLIENT_ID}&client_secret=${
     process.env.REACT_APP_GITHUB_CLIENT_SECRET}`
  );
    setUser(res.data);
    setLoading(false);
  }
  // Get user repos
  const getUserRepos = async (username) => {
    setLoading(true);

    const res = await axios.get(
      `https://api.github.com/users/${username}/repos? 
       per_page=5&sort=created:asc&client_id=${
       process.env.REACT_APP_GITHUB_CLIENT_ID}&client_secret=${
       process.env.REACT_APP_GITHUB_CLIENT_SECRET}`
     );

    setRepos(res.data);    
    setLoading(false);
   }`

User component code.

  useEffect(() => {
        getUser(match.params.login);
        getUserRepos(match.params.login);
    // eslint-disable-next-line
   }, []);

I've recorded a video, so you guys can easily understand what I am trying to say. Video link

Check live app

How can I solve this problem?

Thank in advance!


Solution

  • Here is what happens in the app :

    1. When the App component is rendered the first time, the state is user={} and loading=false
    2. When you click on a user, the User component is rendered with props user={} and loading=false, so no spinner is shown and no data.
    3. After the User component is mounted, the useEffect hooks is triggered, getUser is called and set loading=true (spinner is shown) then we get the user data user=user1 and set loading=false (now the user data is rendered)
    4. When you go back to search page, the app state is still user=user1 and loading=false
    5. Now when you click on another user, the User component is rendered with props user=user1 and loading=false, so no spinner is shown and the data from previous user is rendered.
    6. After the User component is mounted, the useEffect hooks is triggered, getUser is called and set loading=true (spinner is shown) then we get the user data user=user2 and set loading=false (now the new user data is rendered)

    One possible way to fix this problem :

    • instead of using the loading boolean for the User component, inverse it and use loaded
    • When the User component is unmounted clear the user data and the loaded boolean.

    App component:

     const [userLoaded, setUserLoaded] = useState(false);
    
     const getUser = async username => {
        await setUserLoaded(false);
    
        const res = await axios.get(
          `https://api.github.com/users/${username}?client_id=${
            process.env.REACT_APP_GITHUB_CLIENT_ID
          }&client_secret=${process.env.REACT_APP_GITHUB_CLIENT_SECRET}`
        );
    
        await setUser(res.data);
        setUserLoaded(true);
      };
    
      const clearUser = () => {
        setUserLoaded(false);
        setUser({});
      };
    
    <User
      {...props}
      getUser={getUser}
      getUserRepos={getUserRepos}
      repos={repos}
      user={user}
      loaded={userLoaded}
      clearUser={clearUser}
    />
    

    User component:

      useEffect(() => {
        getUser(match.params.login);
        getUserRepos(match.params.login);
        // eslint-disable-next-line
    
        return () => clearUser();
      }, []);
    
      if (!loaded) return <Spinner />;
    

    You can find the complete code here