myTest532 myTest532
myTest532 myTest532

Reputation: 2391

React hooks rendering component before useEffect finishes

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

Upvotes: 7

Views: 12969

Answers (4)

Anthony N
Anthony N

Reputation: 972

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.

Upvotes: -4

Vikas Sharma
Vikas Sharma

Reputation: 735

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

Upvotes: 2

dglozano
dglozano

Reputation: 6607

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;

Upvotes: 4

CertainPerformance
CertainPerformance

Reputation: 371168

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>;

Upvotes: 8

Related Questions