Redux unsubscribe within componentWillUnmount still calls subscribe callback - reactjs

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.

Related

React Redux double mount with useEffect cleanup of previous component

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?

this.setState() not working properly - react.js

componentWillMount() {
let dID=this.props.match.params.datumID;
this.setState({datumID: dID});
console.log(dID);
console.log(this.state.datumID);
}
I'm just trying use setState() method.
But the problem is it is not working.
Here is what I get as output:
First console.log() : 66
Second console.log(): null
this.setState accepts a second argument as callback which is fired after the state is successfully changed.
componentWillMount() {
let dID=this.props.match.params.datumID;
console.log(dID);
this.setState({datumID: dID},()=>console.log(this.state.datumID));
}
side note : Move your code to componentDidMount.
componentWillMount() is invoked immediately before mounting occurs. It
is called before render(), therefore setting state in this method
will not trigger a re-render. Avoid introducing any side-effects or
subscriptions in this method.
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
If you need to set the state based on the previous state,
read about the updater argument below.
this.setState((prevState, props) => {
return {datumID: props.match.params.datumID};
});
Because SetState() is an asynchronous function, use below code
componentWillMount() {
let dID=this.props.match.params.datumID;
this.setState({datumID: dID},
function(){
console.log(this.state.datumID)});
console.log(dID);
}
and if possible try to avoid compenentWillMount since it would be deprecated soon
setState() being an asynchronous function it is in progress of updating the state and javascript being single-threaded language it will execute the next line of code. So if you want to see the state value set you have to do something like this.
componentDidMount=()=> {
let dID=this.props.match.params.datumID;
this.setState({
datumID: dID
},()=>{
console.log(dID);
console.log(this.state.datumID);
});
}
Also I would recommend you to use componentDidMount as willMount won't work in the future as it is deprecated
this.setState is an asynchronous function that takes time and passes the newly assigned
state value over all of the React's life-cycle functions in order to update a state and trigger the re rendering of a component. Your first console logs out the value that is assigned to set state and the next console logs out the value that is currently residing in the state this.state.datumID. if you need to perform operations after setting state you can pass a callback that is triggered after a state is successfully updated.
this.setState({datumID: dID}, () => console.log(this.state.datumID) ); // this will log the updated value
Another thing I want to point out is componentWillMount will wont work in the future release. If you want to do something after DOM is rendered, use componentDidMount or you can perform pre-render task in the constructor of your Class Component.

Render is called twice when fetching data from a REST API

I am trying to interact with a REST API using React, and I have realized that when I fetch the data, render is called once without the data, and then again with the data.
This throws an exception when I try to process this data, but I can use an if statement to check if data is null or not. However, I am not sure if that's needed.
class App extends Component {
state = {
TodoList: {},
};
componentWillMount() {
axios.get("http://localhost:5001/1").then((response) => {
this.setState({
TodoList: response.data,
});
});
}
render() {
console.log(this.state);
return <h1>hello </h1>;
}
}
This is what I see in in the console:
That's perfectly normal.
Your App component flow as below:
Execute render method to load the component
execute codes in componentDidMount
Calling axios.get which is async operation
Receive data from step 2, update component state by using this.setState
App component detected there's an update on state, hence execute render method to load component again
Hence, you should definitely handle the case where this.state.TodoList has no data, which happened at first load
UPDATES:
component lifecycle componentWillMount is now deprecated which means you shouldn't be using it anymore. Replace it with componentDidMount instead. Functionally wise they should be no difference in your example
Initially, render method is called after cwm method. So console log shows the state's empty value first time.
But you have run an async operation in cwm method, so after it is done, the setstate method is called which causes the render method to run again.
Note: ComponentWillMount, componentWillUpdate and componentWillUpdate props method are deprecated.
You should move this API call to componentDidmount or ComponentDidUpdate method.
However, event after this, your console log will appear twice- one for initial render and second for setstate called after API call.
Note : componentWillMount is deprecated and will work until version 17.
Source - https://reactjs.org/docs/react-component.html#unsafe_componentwillmount
Most suitable React Lifecycle for calling APIs:
componentDidMount is the most prefered method to perform asynchronous tasks like API calls, setTimeouts , etc..
It'll work as -
On componentDidMount your API gets called
As per lifecycle order, lastly render method will be called. (Still API hasn't returned response). Your UI is displayed at initial paint.
Once API gets response, you use this.setState() which will force a re-render operation.
Again your UI changes are updated
Remember : this.setState() will only be called once whether you have called it once or more than once in lifecycle method

componentDidUpdate called twice every route change

In my application i have a HomePage component which has navlinks in it. the route for this component is <Route to="/browse/:item" exact component={HomePage} />. so the component has a navigation bar with NavLink links to "sub routes" of it. for example a NavLink that leads you to /browse/featured or
/browse/new and i added to this component the lifecycle method componentDidUpdate() to just console.log("UPDATED HOMEPAGE") and whenever i click a NavLink this happens:
i tried to use shouldComponentUpdate with the nextProps and nextState params to see if indeed the state or props are changed (something that will cause a re-render) but they stay the same.
Thanks in advance for your help.
EDIT:
the code is on github https://github.com/idanlo/spotify-test-frontend
components that have the problem that i have seen are AlbumView and HomePage
ANOTHER EDIT:
this is a console.log() of the two updates happening, each one displaying the props before and after the update. on the first update you can see that the url is different so the update is supposed to happen but on the second update nothing is different, everything is the same (in the state everything is the same too)
Usually there are multiple calls because of changes made to the state. Check other files to make sure that no actions that modify the state are initially called. Even though you check for differences for nextProps, the variable that changes might not be in props.
I suspect that Navlink's internal implementation uses setState with an updater function which is the source of this duplicate log
I found that when I update state with an updater function then componentDidUpdate gets fired twice!
However when I pass an object to setState - componentDidUpdate is fired only once.
setState(updater[, callback])
example:
incrementScore = () => {
this.setState(prevState => ({
score: prevState.score + 1
}));
};
VS
setState(stateChange[, callback])
example:
this.setState({quantity: 2})
I guess it's because
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
read about setState on React Docs

React lifecycle methods

I have been doing react for a while and am familiar with some lifecycle methods but not so familiar with others
e.g. didMount is clearly for ajax requests or calling data from an api then loading it into the app
I think I have conquered shouldComponentUpdate, and have realised it is purely there for performance
but static getDerivedStateFromProps I cant really get my head around. is that for performance or does that add something else to the app?
and also componentDidUpdate, is this for performance again or where is a good example where I can use these?
clearly some methods are necessary to perform actions and actual requests. clearly some are there to improve performance etc. just would like to get some context around didUpdate and getDerived
thanks
Yo can understand the lifecycle hooks in react through this blog : https://medium.com/#baphemot/understanding-reactjs-component-life-cycle-823a640b3e8d
Lifecycle methods made to allow you to run code at particular times in the process.
componentDidMount() is invoked immediately after a component is inserted into the tree, we commonly use it to make API requests.
Using React Hooks
useEffect(() => {
makeApiRequest()
}, [])
componentDidUpdate() is invoked immediately after updating occurs (in the state or props). This method is not called for the initial render. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request
Using React Hooks
useEffect(() => {
doYourStuff()
})
The difference between componentDidUpdate and the Hook above that the Hook will be called in the initial render also. There is no 100% alternative to the componentDidUpdate() method.
componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions.
Using React Hooks
useEffect(() => {
return () => {
cleanUp()
}
})
getDerivedStateFromProps() is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
Read More -> React Lifecycle React Hooks

Resources