React Synchronising Side-Effect With App State
January 11, 2022
- What we want to keep in state
- Data fetching
- Data as streams
What do we want to keep in state?
When a value got changed, React is going to re-render the page. React also tries to avoid unnecessary re-renders. The state should be with values which when updated we will want to update the DOM.
this.setState is asynchronous. If increment function becomes
The final value for the counter is going to be 5. React won’t change the DOM on every line that updates the state. Instead, it will wait for the function to finish and then it will figure out what changes need to happen. At the first line reads set counter to 0 + 1, on the next line 0 + 5. It’s happening because we’re passing an object and the operation is similar to Object.assign and spread operator, the last object wins
Because we can’t merge functions but we can pass a function and the function will work as expected.
useEffect is a hook that allows us to maintain the side effects in the application. If we remove the dependencies array, the effect will run on every render. If it’s empty  ( like in the example ), it will run only when the component gets mounted. When we put dependencies [location, userName] it will execute when some of them update.
On every refresh, we have a new number:
The code is trying to achieve numbersapi.com with a random number. Before the request we set the loading value to true and when the response comes it switches back to false, the number is saved in the array numberArr. If we want to get a new number when we click on a button, we might give some variable in the dependencies array. We have to define when we want to run the effect again.
We can move the random number in the state, and to get a new one when we click on the button and then fetch the number:
If we open the console now, we’ll see that the Number component is re-rendering multiple times.
If we use React.memo which will only re-render the component when some of the props become changed.
Data as streams
Instead of thinking about the data as a collection, we can imagine them as streams. With iterators, we can request data one at a time, while with an observer pattern the data arrives over time. If we want to search for a specific number and we use the same fetchNumber function and useEffect and add input field.
And if we are interested in 2385, we type 2 then 3 then 8 and 5.
On each input, we send a request. There is a race condition. We send the request but the expected result might come before some of the previous. 235 can come later than 2385. With the help of RxJS Library, we can find a solution. fromEvent is a function on which we are passing the target — a form, a button, a document and the event that is going to be emitted from the target — click, scroll, etc. Interval is a function that will suspend the emitting of the observable.
The observable is an object with forEach function and this function accepts an observer, which is an object with 3 functions — onNext, onError and onComplete. In React when we want to keep track of an HTML element — div, input, etc. with the hook useRef we won’t modify the defined piece of the state through the life cycles.
The events that we’re going to listen to are onKeypress and onChange. We are passing to fromEvent the ref and the event we’re interested in and then we merge them. Throttle instead of returning immediately the observable will wait the defined amount of milliseconds before another one comes, if it arrives then the previous gets thrown. If no one arrives after 300 milliseconds, it goes forward.
But this only fixes the problem with network requests.
If we change the throttle with switchMap. So if we start typing and clicking, the request to the API will be only the latest one. The observables have to clean after themselves, we do this in useEffect hook. Ben Lash says about useEffect and unsubscribe in his post “useEffect already has cancellation ala “switchMap” to some degree. When it returns it’s going to register a teardown function and if you provide dependencies they will trigger it to resubscribe and then it will register a new teardown:
The last optimisation that we can do is to make a call to the API only for new values. useCallback is a hook to which we pass a function that only will run if the property changes.