What does it mean by useState are asycnhronous in react - reactjs

I found some old post that a guy having problem with his code in React and alot answer said that it was caused by because useState is asycnhronous. I was curious what it mean by asycnhronous and I found this article
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
But i still don't understand what it means and why is such a problem. Can someone give me an example?

I would say this is one of the myth to call useState asynchronous.
IMHO, if you open the source code of react, I'm speaking to react 16.x and below (because i didn't read after 16), useState isn't asynchronous.
So why are we saying useState behaves as asynchronous code? We need some coding example.
const App = () => {
const [value, setValue] = useState(1)
const onClick = () => {
setValue(2)
console.log(value)
}
return <input value={value} />
}
In the above typical setup of useState, when you call setValue(2), value wouldn't become 2 right away. This is apparent if you put a console.log after that. Then why the input does show 2 instead of 1, you might ask?
That's because React made a second render to App, you can think of App has been called one more time after setValue. For this reason, people call useState async.
But then why I'm not calling useState async? well, this is a bit long story. The short story is that useState is implemented inside React using plain non-async codes, therefore you can't say a code async just because it has been called twice or it has been deferred to call. Async means a bit differently in Javascript. This is another topic which I won't get into here.

I don't want to speak for the React team as to why state changes are asynchronous, but here's some random dude's explanation. I'll be referring to the JavaScript event loop, which is basically a program that runs many times per second. Each time it runs, it processes a series of tasks synchronously. Asynchronous code is executed across several event loops. This is a very basic and oversimplified explanation - you should go read more about the event loop.
So your initial state declaration happens in your initial render - the first time the function is called. This initial render happens synchronously - meaning the function is executed top to bottom in a single event loop. It's worth pointing out that React components cannot be async:
const SomeComponent = () => {
const [foo, setFoo] = useState("bar");
return <div>The value of foo is "{bar}"</div>;
}
After your initial render, the state only changes when something happens: user clicks a button, the screen resizes, a timer interval fires, etc. Whenever an "event" happens, there can be a lot of processing going on in the JavaScript event loop.
Let's look at a button click, the event propagates all the way down and back up the entire DOM tree which contains the button. The browser is also rendering different states of the button, causing the browser to repaint. You also have developer code which is running in the button click handler which might be doing some heavy lifting like iterating over an array. This all happens within a single event loop. The point of this explanation is to convey that "a lot is going on". Eventually, the state will get updated with setFoo(...).
At this point, React now has to figure out which component changed, calculate and rerender your application in a virtual DOM, perform a diff with the actual DOM, and then apply any changes to the actual DOM. Only at this point does the browser finally paint the result to the screen. This whole operation can be very taxing on a CPU.
If react were to handle state changes synchronously, all of this virtual DOM diffing and calculating would happen in the same event loop as the initial button click. It is very likely that most apps would cause the browser to sporadically freeze and jitter because the CPU is doing so much work at one time.
There are also some other things to consider. Suppose the window resizes and a bunch of different components perform some calculations and update their state. You don't want react to synchronously rerender every time a component changes its state. You want react to kind of wait and collect a whole bunch of state changes and process them at the same time. Browsers also provide mechanisms like requestAnimationFrame to help web-based applications asynchronously render in such a way that works best for the user and their system/browser resources.
There are many other things to consider, but this post is getting long. Hope this helps.

Related

Is ResizeObserver API necessary or preferred in React?

We can already detect dimension change by using ref and useEffect in React:
useEffect(() => {
console.log("height changed to", ref.current?.clientHeight)
}, [ref.current?.clientHeight])
I wonder what is the advantage of ResizeObserver over this ref + useEffect approach? I'm surprised there's no online discussion around this, given ResizeObserver has been there for awhile since 2018. I can see if you're using Vanilla Javascript then ResizeObserver might be the best way to get dynamic height.
But in React I just don't understand how ResizeObserver API is better. I tested both and in Chrome both fire updates almost at the same time. I researched online and many are talking about how to use React Hook with ResizeObserver, but rarely do these posts talk about ref + useEffect already suffice.
Does anyone know if using React, any advantage using ResizeObserver API, or it really doesn't differ much from ref + useEffect approach?
We can already detect dimension change by using ref and useEffect in React:
For a useEffect to run, the component must be rendering. For the component to be rendering, something somewhere must have set state. So if all of that is happening, then yes, your effect can notice the size has changed since the previous render.
But if nothing sets state, then none of that will happen. Maybe you're getting lucky, and some component happens to be setting state around the time the size changes, but that's not something you should rely on. If you delete some random component, or try to optimize performance by skipping rendering, you can suddenly break your resize "detection" code.
The point of the resize observer is to get notified that the change has happened. Then you can set state, and cause the rerender. So now instead of hoping a rerender happens, you know it will happen.

Making 2 changes to the state that cancel each other not re-triggering useEffect

I'm seeing something unexpected in this code example here: https://codesandbox.io/s/react-redux-application-forked-lb7zk?file=/src/pages/DashboardPage.js:415-452
I'm logging the change of the x variable (that comes from redux state).
I dispatch 2 actions that would cancel each other:
x starts as 1
first dispatch changes it to: x=2
second dispatch changes it back to: x=1
I was expecting to see 3 console.logs, one for the initial render, and two more for each change described above.
But instead I'm only getting 1, the one for the initial render.
It seems that since they all happen in a sudden, redux seems to batch the actions so the state doesn't really changes in this case.
This really makes me doubt, I'd like to understand what's going on., Is this what's going on?
Edit: React batches state updates that occur in event handlers and lifecycle methods. Thus, if you update state multiple times in UseEffects hook, React will wait for event handling to finish before re-rendering.
Related Question
You are correct. What's going on is that its running all 3 actions once and console.log() is running after the state is updated. It's essentially running this:
useEffect(() => {
dispatch(test(2));
dispatch(test(1));
console.log("Did X change?", x);
}, [dispatch, x]);
I believe the reason is automatic batching in React. As we know "React groups multiple state updates into a single re-render for better performance".
For more information, please check out this great discussion in React 18 Group:
https://github.com/reactwg/react-18/discussions/21
I hope it's useful to help you to discovery your problem.

Dispatch redux action before render with react hooks

So, as the title says: how can we dispatch a redux action before rendering a component?
I have a specific case where i need some shared state chunk cleared before component renders (in that case showing “loading...”) which is done through dispatching redux action.
Now, if i dispatch it with useEffect i get flickering of first stale/old data shown and then showing “loading...”.
So far i see 2 ways to solve it:
useLayoutEffect - i like it but not sure if its a good practice
redefine redux model - that one i would like to avoid plus it sounds a bit wierd
I might create custom Fetcher hook but doesnt that bring it back into realm of hocs/wrapper hells?
I just want to do something before first render.
The difference between useEffect and useLayoutEffect is that the latter is executed synchronously after initial render:
The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
If flickering is the only problem with useEffect and it disappears with useLayoutEffect then the latter should be used.

React useEffect hook not calling mocked function

I am new to React hooks. I'm using a useEffect() hook in a component, and that hook calls a function, searchForVideos() from my props:
useEffect(() => {
props.searchForVideos();
}, [currentPage]);
That function is mocked in my unit tests using Jest:
const searchForVideos = jest.fn();
So, based on my understanding, useEffect() should run for the first time immediately after component render. I can put a console.log() statement in the useEffect() callback and it does print to the console. However, this expect() statement fails:
const component = mountComponent();
setImmediate(() => {
expect(searchForVideos).toHaveBeenCalled();
});
This is strange, because I've confirmed the hook is running, and if it is running it should call the function. However, that line always fails.
Is there something I should know about to make the mocked functions work well with React hooks?
Update
In response to a comment, I made a change that fixed the problem. I do not understand why this worked, though.
const component = mountComponent();
requestAnimationFrame(() => {
expect(searchForVideos).toHaveBeenCalled();
done();
});
So I just replaced setImmediate() with requestAnimationFrame(), and now everything works. I've never touched requestAnimationFrame() before. My understanding of setImmediate() would be that it basically queues up the callback at the back of the event queue right away, so any other JavaScript tasks in the queue will run before it.
So ideally I'm seeking an explanation about these functions and why this change worked.
As far as your secondary question about why requestAnimationFrame fixed it, see the documentation excerpts below. It just gets into the specific timing of useEffect and useLayoutEffect -- specifically "useEffect is deferred until after the browser has painted". requestAnimationFrame delays your test code in a similar fashion. I would expect that if you changed your code to use useLayoutEffect, the original version of your test would work (but useEffect is the appropriate hook to use for your case).
From https://reactjs.org/docs/hooks-reference.html#timing-of-effects:
Unlike componentDidMount and componentDidUpdate, the function passed
to useEffect fires after layout and paint, during a deferred event.
This makes it suitable for the many common side effects, like setting
up subscriptions and event handlers, because most types of work
shouldn’t block the browser from updating the screen.
However, not all effects can be deferred. For example, a DOM mutation
that is visible to the user must fire synchronously before the next
paint so that the user does not perceive a visual inconsistency. (The
distinction is conceptually similar to passive versus active event
listeners.) For these types of effects, React provides one additional
Hook called useLayoutEffect. It has the same signature as useEffect,
and only differs in when it is fired.
Although useEffect is deferred until after the browser has painted,
it’s guaranteed to fire before any new renders. React will always
flush a previous render’s effects before starting a new update.
From https://reactjs.org/docs/hooks-reference.html#uselayouteffect:
The signature is identical to useEffect, but it fires synchronously
after all DOM mutations. Use this to read layout from the DOM and
synchronously re-render. Updates scheduled inside useLayoutEffect will
be flushed synchronously, before the browser has a chance to paint.
Prefer the standard useEffect when possible to avoid blocking visual
updates.

When exactly is `componentDidMount` fired?

This is a recurring problem I have with React. The componentDidMount method is guaranteed to be fired when the component is rendered for the first time so it seems like a natural place to take DOM measurements like heights and offsets.
However, many times I receive wrong style readings at this point of the component's lifecycle. The component is in the DOM, when I break with the debugger, but it's not yet painted on the screen. I get this problem with elements that have width/height set to 100% mostly.
When I take measurements in componentDidUpdate - everything works fine, but this method will not fire at the initial render of the component.
So my question is - when exactly is componentDidMount fired because it's obviously not fired after all the browser paints are done.
EDIT:
This Stackoverflow issue deals with the same subject:
It also references this github conversation that explains what happens
Inside a react component tree, componentDidMount() is fired after all children components have also been mounted. This means, that any component's componentDidMount() is fired before its parent has been mounted.
So if you want to measure DOM position and sizes etc, using componentDidMount() of a child component is an unsafe place/ time to do this.
In your case: to get accurate reading from 100% width/height components, a safe place to take such measurements would be inside the componentDidMount() of the top react component.
100% is a width/height relative to the parent/ container. So measurements can only be taken after the parent has been mounted too.
As you may know, componentDidMount is triggered only once immediately after the initial rendering.
Since you are taking measurements, it would seem you would want to also trigger your measuring when componentDidUpdate in case your measurements change when your component updates.
Please note that componentDidUpdate does not occur for the initial render so you likely need both lifecycle events to trigger your measurement handling. See if this second event triggers for you and if it has different measurements.
In my opinion you should avoid using setTimeout or requestAnimationFrame when possible.
React Lifecycle Reference.
It is called only once when the component mounted. That’s the perfect time to do an asynchronous request to fetch data from an API. The fetched data would
get stored in the internal component state to display it in the render() lifecycle method.
Simple: It Run After Render Functions
If you want to respond to something being mounted in the DOM, the most reliable way to do that is with a ref callback. For example:
render() {
return (
<div
ref={(el) => {
if (el) {
// el is the <div> in the DOM. Do your calculations here!
}
}}
></div>
);
}
you can try delaying logic in componentDidMount() with requestAnimationFrame(). the logic should occur after the next paint.
however, we'd need to know more about your code to see why the nodes haven't been painted. i've never hit that problem. componentDidMount() fires right after the dom nodes are added to the page, but not necessarily after they have been painted.

Resources