reactjsreact-hooksfetchpreload

React. Loading data before rendering


I would like to know how to correctly load data before rendering in React.

I have app, that fetchs data about Star Wars character from API and renders it on the page. I want also to fetch next character data before i want to render it by clicking on the button. I have an example of this app:

`

function fetchData(count, setState) {
    fetch("https://swapi.dev/api/people/" + count)
    .then(res => res.json())
    .then(data => setState({
        id: count,
        name: data.name,
        birth_year: data.birth_year,
        gender: data.gender
    }))
}

function App() {
    let [data, setData] = React.useState({});
    let [preloadedData, setPreloadedData] = React.useState({});
    let [count, setCount] = React.useState(1);

    React.useEffect(() => {
        if (count === 1) {
            fetchData(count, setData)
            fetchData(count + 1, setPreloadedData);
        }    
        else {
            setData(preloadedData)
            fetchData(count + 1, setPreloadedData);
        }    
    }, [count]);

    console.log(count)
    
    return (
        <main>
            <h1>Starwars character data</h1>
            <p><span>Number:</span> {data.id}</p>
            <p><span>Name:</span> {data.name}</p>
            <p><span>Birth year:</span> {data.birth_year}</p>
            <p><span>Gender:</span> {data.gender}</p>
            <button onClick={() => setCount(count + 1)}>Next character</button>
        </main>        
    )
}

`

I have 2 questions: 1)is this a correct way of solving my problem, or there can be better practices? 2)I guess this way still could be better, because console.log is called 3 times, so my component renders 3 times each time the count state is changed, and it could be less?

Would like to know your opinion


Solution

  • 1)is this a correct way of solving my problem

    It seems to work, which is already an acceptable solution.

    For sure it has some limitations, but depending on your actual requirements, you may bare with them. In particular, if user clicks quickly several times on the "Next character" button (before next data is received), you may de-sync count and data, and if the several created fetch requests give unordered responses, preloadedData may also not reflect count + 1.

    2)I guess this way still could be better, because console.log is called 3 times, so my component renders 3 times each time the count state is changed, and it could be less?

    You do not necessarily have to worry about re-rendering (in the sense that the component function is re-executed): React updates its Virtual DOM, but if it sees no change in the output, it does not touch the real DOM (hence almost no UI impact).

    In your case:

    1. count is incremented, but your template is unaffected => no DOM change
    2. data is changed, which affects the template values => DOM is modified
    3. preloadedData is updated, not affecting the template => no DOM change

    there can be better practices?

    We can always improve our code!

    In your case, you could address the above mentioned limitations, and at the same time potentially provide the extra feature of enabling "previous character" navigation, while still re-using preloaded data.

    For that, we would need to accumulate preloaded data with their associated id (count), e.g. with react-use useList hook:

    const [count, setCount] = React.useState(1);
    const [list, { updateAt }] = useList([]);
    
    const data = list[count - 1] ?? {}; // index/position is 0-based, whereas count starts at 1, so position is offset by -1 compared to count
    
    function addItem(atPosition) {
      updateAt(atPosition, {}); // Empty data for now
      fetchData(
        atPosition + 1,
        // Store received data at the correct position
        (preloadedData) => updateAt(atPosition, preloadedData)
      );
    }
    
    // Initially fill the first 2 items
    React.useEffect(() => {
      addItem(0);
      addItem(1);
    }, []);
    
    function handleClickNext() {
      const nextCount = count + 1;
      setCount(nextCount);
      // Ensure list always has at least 1 more item than nextCount
      for (let atPosition = list.length; atPosition <= nextCount; atPosition += 1) {
        addItem(atPosition);
      }
    }
    
    function handleClickPrevious() {
      if (count > 1) { // Prevent navigating to count <= 0
        setCount(count - 1);
      }
      // No need to update list, necause it should already have been populated when navigating up
    }
    
    return (
      <main>
        {/* data display... */}
        <button onClick={handleClickPrevious} disabled={count <= 1}>Previous character</button>
        <button onClick={handleClickNext}>Next character</button>
      </main>
    );