Search code examples
reactjsreact-hooksuse-state

Is using useState with promise just to set value once a good practice?


I was wondering if using useState in React just to set value once is considered a good practice and if not, how can it be improved.

Let's say that I have a simple component, that just fetches some data and displays them in a dropdown:

import React, {useState, useEffect} from 'react';
import {utilities} from './utils';

const Component = (props) => {
    const [ingredients, setIngredients] = useState([]);

    useEffect(() => {
        new Promise((resolve, reject) => {
            const res = utilities.getIngredients();
            if (res.data) {
                resolve("Stuff worked!");
            }
            else {
                reject(Error("It broke"));
            }
        }).then((response) => {
            setIngredients(response);
        });

    }, []);

    return (
        <div>
            <p>Choose from possible ingredients:</p>
            <Select options={ingredients}/>
        </div>
    );
}

export default Component;

Since setIngredients is only called once, is it not too much to use useState here? My intuition tells me that there might be a better way to do it. On the other hand when I tried just using a variable, then the values wewe not updated and dropdown was not showing any options (what makes sense, since data arrived after the dropdown was rendered). Can you please judge what could be improved here?


Solution

  • State is used when persistence across renders is needed. If your component is re-rendered (React to decides when this happens), unsettled Promises (like the ones used by Axios or in the example in your question) will still settle and can assign values to variables - but those variables don't really exist any more. When a Function Component is rerendered, any declared variable is initialised as if this was the first run of the Function Component (it really is just a function). Any reference to a "plain" variable (declared the traditional way let, const or var, rather than by React Hooks) held by the Promise will no longer be the same variable used by the Function Component on the next run.

    Here's an example to illustrate:

    const Component = (props) => {
        let nonStateIngredients = []; //will get initialised to [] every render
    
        useEffect(() => {
            doSomethingThatTakesAWhile()
              .then((response) => {
                // If the Component has been re-rendered
                // by the time we get here, the next line will not
                // do anything
                nonStateIngredients = response.ingredients;
                // If not, the above line will work,
                // but as soon as we rerender, it will be reinitialised 
                // to []
              });
        }, []);
    

    Anything that is not part of a React Hook will essentially be re-initialised.

    So, in React, we use:

    • "Plain" (let, const) variables to store data is derived from existing State or Props within a render, or is constant for the lifetime of the Component
    • State to store data that must persist across renders (data that can change over time)
    • Memos (or Memoized variables) to store data that is always derived, but is expensive (takes a lot of processing power) to derive (this is an optimisation of a "plain" variable)

    To answer your question more directly, when we use State to retrieve and assign a value, it's not best practice - it's the only thing that works.