skip to Main Content

Im currently following a react course and got stuck with this question, in this code snippet im trying to fetch repositories using github API and while data is in process I used a state isLoading which has an initial value of false, and inside the async function this state will be toggled to true and then to false after the request is done, and im wondering why the "Loading…" text appears since react batches all updates until the end, why and how the JSX get access to the true value of the isLoading state? And thanks!

This is the component im using

function App() {
    const [isLoading, setIsLoading] = useState(false);
    const [repos, setRepos] = useState([]);

    useEffect(() => {
        async function fetchRepos() {
            setIsLoading(true);

            const reposFetch = await fetch("https://api.github.com/users/OthmaneNissoukin/repos");
            const data = await reposFetch.json();

            setIsLoading(false);
        }
        fetchRepos();
    }, []);

    return (
        <div className="App">
            <h1>Hello CodeSandbox</h1>
            {isLoading ? <h2>Loading...</h2> : <div>Done!</div>}
        </div>
    );
}

2

Answers


  1. Chosen as BEST ANSWER

    After some researches I found that react does run the setStates update functions inside a batch that runs synchronously which will handle all the changes for updating and perform only one re-render in case if the setState is being performed in a synchronous function (like an event handler).

    For example:

    function App() {
    const [counter, setCounter] = useState(0);
    
    useEffect(() => {
        // batch starts
        setCounter((counter) => counter + 1);  // schedule render
        setCounter((counter) => counter + 1); // schedule render
        setCounter((counter) => counter + 1): // schedule render
        setCounter((counter) => counter + 1); // schedule render
        // Run scheduled render and update to 4
    }, []);
    
    return (
        <div className="App">
            <h1>Hello CodeSandbox</h1>
            <h3>Counter: {counter}</h3>
        </div>
    );
    

    }

    However, when trying to update the states inside an async function then the changes will be handled outside the batch which will lead to re-render for every useState update inside the async function.

    After some researches I found that react does run the setStates update functions inside a batch that runs synchronously which will handle all the changes for updating and perform only one re-render in case if the setState is being performed in a synchronous function (like an event handler).

    const [counter, setCounter] = useState(false);
    
    useEffect(() => {
        // batch already finished
        setTimeout(() => setCounter((counter) => counter + 1), 1000);  // re-render 
        setTimeout(() => setCounter((counter) => counter + 1), 2000); // re-render
        setTimeout(() => setCounter((counter) => counter + 1), 3000): // re-render
        setTimeout(() => setCounter((counter) => counter + 1), 4000); // re-render
        // This will render counter in an increament way since `setTimeOut()` is an async function.
    }, []);
    

    for more details, I recommand checking this article: https://dev.to/adamklein/we-don-t-know-how-react-state-hook-works-1lp8

    And so in my question, the reason why "Loading..." appeared when the fetch has started was due to the async function that prevented the updates from batching all changes until the end and instead caused multiple re-renders whenever isLoading state was changed.


  2. since you dont have the data initially you need to make the default value of is loading to be true in the first render Loading will be displayed , in the second render toggled by the useEffect you fetch the data , set the data to the result of your fetch request and then change the state of loading .
    your code should look like this :

    function App() {
    /* initial state of loading should be true since you have no data yet */
    const [isLoading, setIsLoading] = useState(true);
    const [repos, setRepos] = useState([]);
    
    useEffect(() => {
        async function fetchRepos() {
            
    
            const reposFetch = await fetch("https://api.github.com/users/OthmaneNissoukin/repos");
            const data = await reposFetch.json();
            /* set the the repo to the result of fetch */
            setRepos(data)
            /* change the state of loading so the data is shown */
            setIsLoading(false);
        }
        fetchRepos();
    }, []);
    
    return (
        <div className="App">
            <h1>Hello CodeSandbox</h1>
            {isLoading ? <h2>Loading...</h2> : <div>{JSON.stringify(repos)}</div>}
        </div>
    );
    

    }

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search