React Redux double mount with useEffect cleanup of previous component - reactjs

I'm using react-redux, and I'm trying to clear part of my redux state when a component unmounts, this actually does work, but the problem is that once userProfile is cleared and next component mounts it is unmounted and then mounted again.
useEffect(() => {
getProfileByUsername(match.params.username, user && user.id);
return () => clearUserProfile();
}, [match.params.username])
I can see my clear action is being called before any mount of the next component occurs, but the data is not cleared before the initial mount, so once it does clear, it triggers an unmount and then re-mounts..
I am certain that this is the cause because if I remove the clearUserProfile() cleanup function, it only mounts once.
The other components do not even use userProfile, and I am able to update the state with other actions without re-renders, so I'm not sure why this is happening. I either need a way to prevent this, or to prevent the routing until the action is fully complete. I'm using react-router-dom.
What would be the correct strategy to clear part of my state when a component unmounts?

Related

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.

Why am I getting an infinite loop inside the component as state is globally managed?

I'm working with redux saga and redux toolkit. In the EachUser component, I don't understand why the component is mounting over and over again if I don't use the useEffect hook. Could someone help me understand why it is.
I'm not changing any props or state so I don't think infinite loop should happen but it is happening.
Here is the link to my code sandbox: Redux Saga With Redux toolkit
Happens following:
When you are trying to dispatch your FETCH_SINGLE_USER action on the top level of your functional component it will be executed on each render cycle (your function is re-rendered(executed) each time when props or state changed, also it will be re-rendered when the parent component is re-rendered (if you don't use React.memo).
So when you are dispatching your action on the top level of your functional component it will
fetch user from the server
->
update your store
->
updated store will cause re-render of the component as you are selecting updated state with useSelector
->
fetch user from the server action dispatched again (we are inside of the infinite loop)
So why we need useEffect - it is the hook that helps us to make some actions on special conditions. In the second parameter, you should put an array of dependencies, once dependency changed it will cause hook rerun. If the array is empty - hook will be executed only once when your functional component mounted. Also you can return cleanup function that will be executed, once your component unmounted. More documentation on useEffect hook here
try to remove dispatch from the array of dependencies
useEffect(() => {
dispatch({ type: sagaActions.FETCH_SINGLE_USER, userId });
}, [userId]);
calling a function inside useEffect and specifying it in an array of dependencies can cause an infinite rerender

Why is componentWillUnmount firing after next components componentWillMount?

I have a React/Redux application with two components. I need to clear a portion of redux state when the first component unmounts, because the second component will error with the state in that form. I've tried to dispatch an action clearing the chunk of state when the first component unmounts, but the second component begins mounting before the first components componentWillUnmount method is called. When I view dispatched actions in redux-logger, I see the second component dispatching actions from componentWillMount and then componentWillUnmount actions from the previous component called.
This is not the expected behavior is it? I am also using react-router v4. Thanks!
Since React v16, the componentWillUnmount hook can fire asynchronously.
This means that you can't make any assumptions about the order (or timings) of the invocations of these hooks cross-component.

Redux unsubscribe within componentWillUnmount still calls subscribe callback

I am manually hooking a React component up to a Redux store. Yes, I realize Dan Abramov of Redux recommends against this. I will be getting around to looking at react-redux eventually, but for now I want to understand what's going on.
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
const storeState = store.getState();
this.setState({
someData: storeState.someData
});
}.bind(this));
}
componentWillUnmount () {
this.unsubscribe();
}
This is some code I have in a tab page component. These tab pages get swapped in and out in a parent component as the user changes tabs to display different data. I have stepped through the debugger and confirmed that as a user changes tabs, the following occurs in order:
The previous tab is unmounted (componentWillUnmount fires and this.unsubscribe() is called. this is the previous tab component, as expected)
The new tab is mounted. (componentDidMount fires and this is the new tab component, as expected)
setState gets called in the subscribe callback with this being the PREVIOUS TAB, and react complains with an error:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the Tab component.
This seems strange to me. Shouldn't calling unsubscribe as I have prevent this callback from being called on the unmounted component? The post that I'm following along with seems to suggest that doing what I've done should make this warning go away, and is simply questioning if this is a good idea. But in my case, it persists.
I don't think it matters, but I am using the ES6 "class" syntax whereas the original appears not to be.
subscribe(listener: () => void): Unsubscribe;
Lets read subscribe method doc.
The subscriptions are snapshotted just before every dispatch() call.
If you subscribe or unsubscribe while the listeners are being invoked,
this will not have any effect on the dispatch() that is currently in
progress. However, the next dispatch() call, whether nested or not,
will use a more recent snapshot of the subscription list.
Since you first have called dispatch method, all listeners are invoken and your unsubsribe call will only take place on next dispatch call.

react-router-redux push, state change reset

I have an application that creates a new record and redirect the history to the new record page using react-redux-router push. This redirection is made by a "smart component" inside another smart component.
The state of these components is stored on a redux store.
After redirection if I go back to the previous page it's state is dirty.
Is there a way to reset the state ? Or should I manually clean it up before the redirection.
Currently I'm listening on the reducer for LOCATION_CHANGE and resetting it but this seems manual and hacky.
Shouldn't the component unmount when it's route is not rendered anymore?
I'm unclear from your question - is the state that you're wanting to be reset stored within that component (setState) or within the Redux store?
If it's within that component, and the component is unmounted, then its state should be automatically reset.
If it's within the Redux store, then the whole point of the Redux store is that it exists persistently, outside of whatever components are mounted. One common way of resetting state in that case would be to dispatch a clear or reset action within the component's componentWillMount (i.e., the component ensures that it always starts with a good state).

Resources