React hooks rendering component before useEffect finishes

Viewed 7179

I have an asynchronous request in my useEffect. I notice that the component is rendered before the useEffect finishes.

import React, { useEffect } from 'react';
import axios from 'axios';

import './SearchWords.css';

const URL_REQUEST = 'http://myLink.com';

const SearchWords = (props) => {
    const { setState } = props;

    useEffect(async () => {
        const data = {
            sentence: props.sentence
        };
      
        await axios.post(`${URL_REQUEST}/getData`, { data })
            .then((res) => {
                setState(state =>({
                    ...state,
                    search: res.data
                }));
            })
            .catch((err) =>{
                console.log(err);
            })
    }, []);

    const render = () => {
        if(props.search.length > 0) {
            const linkMarkup = props.search.map((link) => (
                <li key={link.id} className="link-list-item">
                  <a
                    href={link.link}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="link-list-item-url"
                    onClick
                  >
                    {link.name}
                  </a>
                </li>
            ));
            
            return <ul className="link-list">{linkMarkup}</ul>;
        } else {
            return <p>No data found for: {props.sentence}</p>
        }
    }

    return render()
}

export default SearchWords;

It is working, but it first shows No data found for: My Search String. Then, it reloads and shows the <ul> with the results. How can I avoid it to first show the "No data found" message.

Thanks

4 Answers

The simplest tweak would be to conditionally render that section of the JSX depending on whether the response has come back yet:

const [hasLoaded, setHasLoaded] = useState();
// ...
.then((res) => {
    setState(state =>({
        ...state,
        search: res.data
    }));
    setHasLoaded(true);
})

then change

return <p>No data found for: {props.sentence}</p>

to

return hasLoaded ? <p>No data found for: {props.sentence}</p> : <p>Loading...</p>;

That's how the useEffect works: it will schedule a side effect to be run after the component has rendered.

From the docs:

What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.

What you can do is keep track of your effect status to show a loading indicator until it has finished.

import React, { useEffect } from 'react';
import axios from 'axios';

import './SearchWords.css';

const URL_REQUEST = 'http://myLink.com';

const SearchWords = (props) => {
    const { setState } = props;
    const [isLoading, setLoading] = React.useState(true);

    useEffect(async () => {
        const data = {
            sentence: props.sentence
        };
      
        await axios.post(`${URL_REQUEST}/getData`, { data })
            .then((res) => {
                setState(state =>({
                    ...state,
                    search: res.data
                }));
                setLoading(false);
            })
            .catch((err) =>{
                console.log(err);
            })
    }, []);

    const render = () => {
        if(isLoading) return "Loading...";

        if(props.search.length > 0) {
            const linkMarkup = props.search.map((link) => (
                <li key={link.id} className="link-list-item">
                  <a
                    href={link.link}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="link-list-item-url"
                    onClick
                  >
                    {link.name}
                  </a>
                </li>
            ));
            
            return <ul className="link-list">{linkMarkup}</ul>;
        } else {
            return <p>No data found for: {props.sentence}</p>
        }
    }

    return render()
}

export default SearchWords;

It is working as expected read more about react lifecycle. To handle this case as you're using functional component use useState() hook for API status.

const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
 // api call
 // now after api call set setIsLoading(false);
},[]}

if(isLoading) {
  // return show loading spinner
}

// else remaining code

You want to track if your fetch call resolved before rendering anything on the screen.

You could use React.useRef to keep track of this state.

See CodeSandbox demo here.

Related