Dispatch redux action before render with react hooks - reactjs

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.

Related

React useEffect/componentDidMount vs. state hook + function

It seems to me that one could replace useEffect/componentDidMount with a state hook and function components. For example, if I wanted to make a fetch in useEffect or componentDidMount, I could simply create a function component that rendered the component that needed fetching, add the fetching method in the function (which will execute upon rendering) which modifies a state hook (so that once the data arrives, the page will re-render with the data). Since React has selective rendering, any other part of the function component that gets updated won't cause an unnecessary fetch.
Am I correct in saying this? Are there any other specific instances where useEffect/componentDidMount is strictly better?
Am not sure why you want to replace componentDidMount in class component or useEffect with a custom function for the use case you highlighted but those two are different in terms of behavior, componentDidMount is one functionality that useEffect provides in function component. useEffect is a combination of componentDidMount and componentWillUnmount and you can have mulitple useEffect on one function component. useEffect accept array of values that could make it run again, all you do is specify those value for that particular useCase you wanted and whenever that values changes the useEffect is called, for UI changes you attach your useState to your logic and if followed accordingly you wont get unnecessary fetch request, if you want useEffect to be called just once then attach just an empty array like this useEffect(()=>{//your fetch logic here },[])

Why don't redux actions "break" componentDidMount/useEffect?

A component will rerender when one of its props changes. That's sort of the whole point of React.
A component that's subscribed to a slice of Redux state will rerender when that state changes. That's sort of the whole point of Redux. And when you use the connect api, those slices of state are simply props, so it goes straight to my first point.
SO, what I'm wondering is:
// only executes once, on mount, right?
componentDidMount() {
this.something()
this.props.someReduxActionThatModifiesAnotherPropInThisComponent()
this.somethingElse()
}
Since the redux action changes the prop, and the component rerenders, I would think that componentDidMount has had its day and now it's done, and we'll never run this.somethingElse().
Obviously I am wrong about this. I know. Can someone tell me what it is that makes this work? What is the whole flow here? I imagine the answer is maybe simply that a component doesn't rerender from inside CDM?
And is it any different in useEffect?
You are correct that componentDidMount only runs once. You are also correct that dispatching a redux action from within the method will trigger a re-render if your component is subscribed.
I think the confusion is about when a re-render occurs.
Updating state only informs React that a re-render is required, it does not pause execution and re-render immediately.
Because of this, the lifecycle method will complete execution of the entire method, and the run the scheduled re-render after.
This is related to why you also cannot use the updated state immediately after calling this.setState (or dispatch in Redux). Because the state is not updated instantly, you've only informed it that an update is required. Behind the scenes, React batches and performs the updates, and then determines what re-renders to perform.
The same is true about Function components and useEffect.
componentDidMount runs only once during the mounting process. But even if you replaced componentDidMount with componentDidUpdate, it also wouldn't rerender before executing the whole function.
The reason to this is that it is actually up to React when to re-render. Sometimes, in situations like yours, React decides not to re-render the component immediately and postpone it.
A similar situation would be when same setState functions are called inside a method. The first setState call doesn't force an immediate re-render.

Is `useLayoutEffect` preferable to `useEffect` when reading layout?

One difference about useLayoutEffect vs useEffect is that useLayoutEffect will be fired synchronously after DOM mutation and before the browser paint phase. (Reference)
However, I've came across some articles and they all seem to recommend to use useLayoutEffect for reading layout.
React docs:
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.
Kent C.Dodds:
useLayoutEffect: If you need to mutate the DOM and/or do need to perform measurements.
Ohans Emmanuel:
Where possible, choose the useEffect Hook for cases where you want to be unobtrusive in the dealings of the browser paint process. In the real world, this is usually most times! Well, except when you’re reading layout from the DOM or doing something DOM-related that needs to be painted ASAP.
As I understand, by the time the useEffect is fired, the browser has already passed the layout and paint phase, so reading layout in useEffect should be totally okay.
So my question: Is useLayoutEffect preferable to useEffect when reading layout and if yes, why? Taken that I just only read the layout and don't perform any additional state changes in the effect.
The following diagram always helps me to visualize the flow of hooks and also will let you clarify regarding why useLayoutEffect is recommended over useEffect for DOM based operations (where you're targetting stuff that you can update before current browser paint)
Link to the diagram - https://github.com/donavon/hook-flow
If you don't have DOM manipulating code in the hook and only reading layout useEffect will do just fine.
If you do have DOM manipulating code in the useEffect and see screen flickering move this code to useLayoutEffect.
It goes like this:
Render
useLayoutEffect (synchronously after all DOM mutations)
Paint
useEffect
The answer depends on what you as a developer consider 'preferable'. In the context of your question useLayoutEffect guarantees that you will see layout before mutation, useEffect shows you what happens after.

useEffect or useMemo for API functions?

which is the best hook for dispatching API calls in a component. Usually I use useMemo for calling the API on the first render, and useEffect if I need extra side effects, is this correct? Becouse sometimes I get the following error:
'''index.js:1 Warning: Cannot update a component (Inscriptions) while rendering a different component (PaySummary). To locate the bad setState() call inside PaySummary, follow the stack trace as described in ...''''
That happens when I route to a component and rapidly change to another one, it doesn't "affect" the general behaivour becouse if i go back to the previous component it renders as expected correctly. So how should I do it?
Calling an API is a side effect and you should be using useEffect, not useMemo
Per the React docs for useEffect:
Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.
Per the React docs for useMemo:
Remember that the function passed to useMemo runs during rendering. Don’t do anything there that you wouldn’t normally do while rendering. For example, side effects belong in useEffect, not useMemo.
Performing those side effects (and modifying state) during rendering or with useMemo is the reason you encounter the errors you mention.
basically I rather to use useEffect in componentDidMount manner, with no dependency like below
useEffect(() => {
// Api call , or redux async action here...
}, [])
for calling api's at component mount state.
most of the time i find my self using useMemo for memoising the data at functional Component render level, for preventing the variable re-creation and persist the created data between renders except the dependency changes.
but for the context of your question, there is a hook called useLayoutEffect which is primarily used for actions to happen before painting the DOM, but as i said basically most of the time in projects i find calling apis in a simple useEffect with no dependencies aka, the did mount of your component, in order to load the required data for component!
A bit late but, while everything mentioned above is completely true; the error
'''index.js:1 Warning: Cannot update a component (Inscriptions) while rendering a different component (PaySummary). To locate the bad setState() call inside PaySummary, follow the stack trace as described in ...''''
Has to do with the fact that the API call is Asynchronous and when you rapidly change the pages, the set state call (for updating the data returned from the API call I assume) is still waiting to be called after the data is returned from the API. So, you have to always clean up your Async functions in useEffect to avoid this error.

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.

Resources