gene b.
gene b.

Reputation: 12044

React Hooks (Functional) Lifecycle: Component rendered before useEffect() code

I'm using React Functional Components. I'm familiar with the class lifecycle, but not with the functional lifecycle. My problem is that this component is rendered before the initialization performed in useEffect().. is complete. In my return() I check whether a state variable is true or not.

  1. I see my non-Error layout first (TopNav & Main)
  2. In 2-3 sec. the screen changes to the Error layout

What is the order in which functional components are processed? I can't present then non-Error layout until the error condition has been evaluated.

function App() {

    const [error, setError] = useState({});
    const [userInfo, setUserInfo] = useState({});

    const fetchUserInfo = async () => {
       const url = 'http://myapp.com/fetchUserInfo';
       try {
          const response = await axios.get(url);
          setUserInfo(response.data);
       }
       catch (error) { 
          setError(error);
       }
    }  

    // On initialization, fetch UserInfo
    useEffect(() => {    
        fetchUserInfo();
    }, []);

    return (
        <div className="App">
            <Router>
                <Header />
                
                {error.message
                    ? 
                        <Error error={error} /> 
                    :
                        <div>
                            <TopNav userInfo={userInfo} />
                            <Main userInfo={userInfo} />
                        </div>
                }
                
                <Footer />
            </Router>
        </div>
  );
}

Upvotes: 2

Views: 795

Answers (3)

kiki
kiki

Reputation: 62

Your code will be executed in this order.

  1. Since the error.message is empty for your first render, the TopNav and Main will be rendered.
  2. Execute the useEffect hook, call the fetchUserInfo function. either the error or the userInfo will be updated.
  3. Since the state has been changed, will render the page again. in your case, it will evaluate the error.message and render the corresponding component.

Upvotes: 1

Shubham Khatri
Shubham Khatri

Reputation: 282060

useEffect callback function is called after the render cycle and so once your render is complete, your fetchUserInfo call is made which is again an async operation.

What you should instead have is a loading state to let the UI wait will the API has provided as response to show an appropriate UI

function App() {
    const [isLoading, setIsLoading] = useState(true);
    const [error, setError] = useState({});
    const [userInfo, setUserInfo] = useState({});

    const fetchUserInfo = async () => {
       const url = 'http://myapp.com/fetchUserInfo';
       setIsLoading(true);
       try {
          const response = await axios.get(url);
          setUserInfo(response.data);
       }
       catch (error) { 
          setError(error);
       }
       finally {
           setIsLoading(false);
       }
    }  

    // On initialization, fetch UserInfo
    useEffect(() => {    
        fetchUserInfo();
    }, []);

    return (
        <div className="App">
            <Router>
                <Header />
                
                {isLoading? <Loader />: error.message
                    ? 
                        <Error error={error} /> 
                    :
                        <div>
                            <TopNav userInfo={userInfo} />
                            <Main userInfo={userInfo} />
                        </div>
                }
                
                <Footer />
            </Router>
        </div>
  );
}

Upvotes: 2

Josh
Josh

Reputation: 841

Looking at your code, you will only get an error/data state after the async function fetchUserInfo is resolved/rejected. You can try adding a third state loading to signify that component has mounted but data fetching is in progress.

function App() {

  const [error, setError] = useState({});
  const [userInfo, setUserInfo] = useState({});
  const [loading, setLoading] = useState(true)

  const fetchUserInfo = async () => {
    setLoading(true);
    setError({})
    const url = 'http://myapp.com/fetchUserInfo';
    try {
      const response = await axios.get(url);
      setLoading(false);
      setUserInfo(response.data);
    }
    catch (error) {
      setLoading(false)
      setError(error);
    }
  }

  // On initialization, fetch UserInfo
  useEffect(() => {
    fetchUserInfo();
  }, []);

  return (
    <div className="App">
      <Router>
        <Header />

        {error.message && !loading
          ?
          <Error error={error} />
          :
          <div>
            <TopNav userInfo={userInfo} />
            <Main userInfo={userInfo} />
          </div>
        }

        <Footer />
      </Router>
    </div>
  );
}

Upvotes: 1

Related Questions