Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use the useState hook with asynchronous calls to change an array?

I'm trying to update an array from a put request

const [descriptors, setDescriptors] = useState([]);

const handleDescriptorUpdate = (id, descriptorData) => {
    services
      .putDescriptor(id, descriptorData)
      .then((response) => {
        const descriptorIndex = _.findIndex(descriptors, (e) => e.id === id);
        if (descriptorIndex !== -1) {
          const tempDescriptors = [...descriptors];
          tempDescriptors[descriptorIndex] = response.data;
          setDescriptors(tempDescriptors);
        }
      })
      .catch((error) => {
        console.error(error);
      });
  };

This works fine when I perform only 1 request at the time, but when I click the button that performs the update twice the promises, instead of spreading the array and updating the old value with the new one, both of them are spreading the same array causing that when the second promise is resolved, it updates the state with the new value (second value) that came back from the server BUT changing the first value (the one changed by the first promise) for its original value.

Descriptors are initially filled with an array of objects (from a get request):

[
  {
    "id": 24,
    "name": "Test 4.1",
    "percentage": 0,
    "towerId": "5cfc9200-c04a-11e9-89c0-2dd5d3707b1b"
  },
  {
    "id": 23,
    "name": "Test 3.1",
    "percentage": 0,
    "towerId": "5cfc9200-c04a-11e9-89c0-2dd5d3707b1b"
  }
]
like image 798
Sebastián Suárez Valencia Avatar asked Oct 03 '19 00:10

Sebastián Suárez Valencia


People also ask

How do you update an array using useState hook?

myArray. push(1); However, with React, we need to use the method returned from useState to update the array. We simply, use the update method (In our example it's setMyArray() ) to update the state with a new array that's created by combining the old array with the new element using JavaScript' Spread operator.

Is useState hook asynchronous?

TL;DR: useState is an asynchronous hook and it doesn't change the state immediately, it has to wait for the component to re-render. useRef is a synchronous hook that updates the state immediately and persists its value through the component's lifecycle, but it doesn't trigger a re-render.

Is setState hook asynchronous?

setState() async Issue: If you're using ReactJs, then you're likely familiar with the setState method. This function is used to update the state of a component, but it's important to remember that setState is asynchronous.

What is the useState hook used for what are the two items in the array?

When we declare a state variable with useState , it returns a pair — an array with two items. The first item is the current value, and the second is a function that lets us update it.


2 Answers

I saw you let descriptors as a state instead of a reference, as I said in my comment above useState doesn't reflect changes immediately so keep on memory one reference of your array, you can do it with the hook useRef, see the next example:

const [descriptors, setDescriptors] = useState([]);

const descriptorsReference = useRef(null);

const handleDescriptorUpdate = (id, descriptorData) => {
    services
      .putDescriptor(id, descriptorData)
      .then((response) => {
        const descriptorIndex = _.findIndex(descriptors, (e) => e.id === id);
        if (descriptorIndex !== -1) {
         // Use descriptorsReference instead
          const tempDescriptors = [...descriptorsReference.current];
          tempDescriptors[descriptorIndex] = response.data;
          // Next line is to update the descriptors into descriptors state, this phase doesn't happen immediately 'cause is asynchronous 
          setDescriptors(tempDescriptors);
          // Next line is to update the descriptors in memory, this phase occurs immediately 
          descriptorsReference.current = tempDescriptors
        }
      })
      .catch((error) => {
        console.error(error);
      });
  };
like image 189
DariusV Avatar answered Sep 29 '22 01:09

DariusV


Even though React batches all setStates done during an event handler.

setDescriptors is outside the scope of the event handler for it's only called when promise is resolved.

Thus, you would need to make use of a state callback to properly manage descriptor versions.

.then((response) => {
  setDescriptors((descriptors) => ( // use state callback
    descriptors.map((desc) =>
      desc.id === id ? { ...desc, ...response.data } : desc
    ) 
  })
}
like image 43
Joseph D. Avatar answered Sep 29 '22 01:09

Joseph D.