Home > OS >  React. Loading data before rendering
React. Loading data before rendering

Time:01-03

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

CodePudding user response:

if you don't want that your useEffect execute 3 times you can pass an empty array, this way it will only execute when mounting and unmounting yor component, like this:

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

And yes fetching stuff in the useEffect its good but, for complex apps it is much better to use Redux and fetching the data with a Thunk. Another good practice is cleanning stuff by returning a callback function at the end of your useEffect, like this:

React.useEffect(() => {
        if (count === 1) {
            fetchData(count, setData)
            fetchData(count   1, setPreloadedData);
        }    
        else {
            setData(preloadedData)
            fetchData(count   1, setPreloadedData);
        }  
       return () => {
          //Clean things here
       }  
    }, []);

I don't know if it anwers your question but I hope it helps

CodePudding user response:

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>
);
  • Related