React: setState can only update a mounted or mounting component - javascript

I need a setState inside a timeout in my component, so I've done this:
componentDidMount() {
this.timeouts.push(setTimeout(() => {
this.setState({ text: 2 });
}, 4000));
}
componentWillUnmount() {
this.timeouts = [];
}
But I'm getting this 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.
What am I doing wrong?

Change your componentWillUnmount to clear the timeouts properly. You need to make use of clearTimeout to clear the timeout instead of emptying the array.
componentDidMount() {
this.timeouts.push(setTimeout(() => {
this.setState({ text: 2 });
}, 4000));
}
clearTimeouts: function() {
this.timeouts.forEach(clearTimeout);
}
componentWillUnmount() {
this.clearTimeouts();
}

Related

How to rerun a function (that runs when component is mounted) when the redux store is changed?

As in the title, I have a React component, the relevant part of which looks a bit like this as of now:
class myComponent {
fetchSomething = async() => {
this.setState({data: fetch(props.id)})
}
componentDidMount(){
this.fetchSomething()
}
render() {
{data}
}
}
const mapStateToProps = state => {
...
return { id }
}
export default connect(mapStateToProps)(myComponent);
I need to basically rerun the fetchSomething function whenever the id property in redux store changes, since I want to see the changes as soon as it happens, and I don't want to have to mount the component again.
you can try using componentDidUpdate lifecycle method.
componentDidUpdate(prevProps) {
if(prevProps.id !== this.props.id) {
this.fetchSomething();
}
}
More on componentDidUpdate or other useful lifecycle methods here

React Native: Invariant Violation: Maximum update depth exceeded error

I know this can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate but in my situation even if I call setState just one time after that I got this error. Here is my state and componentDidUpdate:
constructor(props) {
super(props);
this.state = { usernameAlreadyUsing: false };
}
componentDidUpdate() {
if(this.props.usernames.includes(this.props.username)) {
this.setState({ usernameAlreadyUsing: true });
}
}
Every time setState called componentDidUpdate recalls itself. So
this situation causes you to enter an endless loop.
Here try this,
constructor(props) {
super(props);
this.state = { usernameAlreadyUsing: false };
}
componentDidUpdate() {
const { usernameAlreadyUsing } = this.state;
if(this.props.usernames.includes(this.props.username) && !usernameAlreadyUsing) {
this.setState({ usernameAlreadyUsing: true });
}
}
With this implementation your code only enters one time in componentDidUpdate.
Hope it works.
constructor(props) {
super(props);
this.state = { usernameAlreadyUsing: false };
}
componentDidUpdate(prevProps) {
if(this.props.usernames.length != prevProps.usernames.length) {
if(this.props.usernames.includes(this.props.username)) {
this.setState({ usernameAlreadyUsing: true });
}
}
}
try. this, problem with your above is , once its setting username is already present, its again calling setState as the condition succeeds every time. try this
Hope it helps.feel free for doubts

Reactjs state not updating

constructor(props) {
super(props);
this.state = {
active: false,
showSideBar: false,
className: ""
}
}
componentDidMount() {
if (this.props.overlay) {
this.setState({
className: "wrapper_overlay"
});
alert(this.state.className);
}
else if (this.props.SideBarWithIcon) {
this.setState({
className: "wrapper_clopsed"
});
}
}
I am updating my state with the help of the props but the component is getting props but state is not updating
setState is asynchronous. Just alert in a callback to the method instead.
if (this.props.overlay) {
this.setState(
{ className: "wrapper_overlay" },
() => alert(this.state.className);
);
}
Note: you can, and should, also use shouldComponentUpdate to check for when a setState call completes
Since setstate is async in nature so you maynot see updated state in alert.
You can use that in callback which will be called once the setstate is done. like this
componentDidMount() {
if (this.props.overlay) {
this.setState({
className: "wrapper_overlay"
}, ()=> {alert(this.state.className);});
}
State updates may be asynchronous
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
// Wrong
this.setState({
className: "wrapper_overlay"
});
To fix it, use a second form of setState() that accepts a function
rather than an object.
// Correct
this.setState((state, props) => ({
className: "wrapper_overlay"
}));
or just
this.setState(() => ({
className: "wrapper_overlay"
}));
In React.js, running program thread does not wait for setting state and continues its execution, until and unless operation defined in setState() callback.
Your state is getting set but as your alert() is after setstate i.e. not in callback of setState() that's why its getting previous value because at the time state is setting new value, thread is not waiting but executing the next instruction which is alert().
if (this.props.overlay) {
this.setState({ className: "wrapper_overlay" }, () => alert(this.state.className););
}
Hope this works for you.
Just use the callback method to update the state, as
setState() might work asynchronously.
this.setState(() => {
className: "wrapper_clopsed"
});

Can't call setState on an unmounted component (Async still running)

I have an asynchronous function like so:
componentDidMount() {
someAsyncFunction().then((data) => {
this.setState({ something: data });
});
}
If go back to the previous screen, I will get the following error:
Warning: Can't call setState (or forceUpdate) on an unmounted component.
Is there something I can do to cancel this setState() if I go back to a previous screen while the async Is still running?
You can use this workaround:
componentDidMount() {
this._ismounted = true;
someAsyncFunction().then((data) => {
if (this._ismounted) {
this.setState({ something: data });
}
});
}
componentWillUnmount() {
this._ismounted = false;
}
This way the sesState will be called only if the component is mounted.
But this (as suggested in the comments) is an antiPattern and is to be used only when there is no another way to cancel the asyncFunction instead of waiting for it to be solved and then make the check.
First, you should not use isMounted() to wrap your code in an if-Statement.
(https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html)
In your case I think you have several choices: You could fire an action in your asynchronous function which sets the redux state instead of the components state.
Or if you really need it, you could set a flag in your componentDidMount and set it on false in componentWillUnmount.
constructor(props){
super(props);
this.state={
mounted: true
}
}
componentDidMount() {
this.setState({mounted: true});
someAsyncFunction().then((data) => {
if(this.state.mounted) this.setState({ something: data });
});
}
componentWillUnmount() {
this.setState({mounted: false});
}

React fetch data getting lost

I have a component in which I fetch data based on an item ID that was clicked earlier. The fetch is successful and console.log shows the correct data, but the data gets lost with this.setState. I have componentDidUpdate and componentDidMount in the same component, not sure if this is okay or maybe these two are messing eachother up?
Here is the code:
const teamAPI = 'http://localhost:8080/api/teams/'
const playerAPI = 'http://localhost:8080/api/playersByTeam/'
const matchAPI = 'http://localhost:8080/api/match/'
class View extends Component {
constructor() {
super();
this.state = {
data: [],
playersData: [],
update: [],
team1: [],
team2: [],
matchData: [],
testTeam: [],
};
}
componentDidUpdate(prevProps) {
if (prevProps.matchId !== this.props.matchId) {
fetch(matchAPI + this.props.matchId)
.then((matchResponse) => matchResponse.json())
.then((matchfindresponse) => {
console.log(matchfindresponse);
this.setState({
matchData:matchfindresponse,
testTeam:matchfindresponse.team1.name,
})
})
}
}
componentDidMount() {
fetch(teamAPI)
.then((Response) => Response.json())
.then((findresponse) => {
console.log(findresponse)
this.setState({
data:findresponse,
team1:findresponse[0].name,
team2:findresponse[1].name,
})
})
fetch(playerAPI + 82)
.then(playerResponse => playerResponse.json())
.then(players => {
console.log(players)
this.setState({
playersData:players
})
})
}
The first render also gives this warning:
Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Please check the code for the View component.
Everything from ComponentDidMount works fine in render but {this.state.matchData} and {this.state.testTeam} from componentDidUpdate are empty.
Could the problem be that ComponentDidMount re-renders the component which causes the data from ComponentDidUpdate to be lost and if so, how could I fix this?
Tried ComponentWillReceiveProps like this but still no luck
componentWillReceiveProps(newProps) {
if (newProps.matchId !== this.props.matchId) {
fetch(matchAPI + newProps.matchId)
.then((matchResponse) => matchResponse.json())
.then((matchfindresponse) => {
console.log(matchfindresponse.team1.name);
console.log(this.props.matchId + ' ' + newProps.matchId);
this.setState({
matchData:matchfindresponse.team1.name,
})
})
}
}
On your componentDidMount you should be using Promise.all. This isn't really your problem, but it does make more sense.
componentDidMount() {
const promises = [
fetch(teamAPI).then(resp => resp.json()),
fetch(playerAPI + 82).then(resp => resp.json())
];
Promise.all(promises).then(([teamData, playerData]) => {
// you can use this.setState once here
});
}
Looks like your componentDidUpdate should be a getDerivedStateFromProps in combination with componentDidUpdate (this is new to react 16.3 so if you are using an older version use the depreciated componentWillReceiveProps). Please see https://github.com/reactjs/rfcs/issues/26. Notice too that now componentDidUpdate receives a third parameter from getDerivedStateFromProps. Please see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html for more details.
EDIT: Just to add more details.
Your state object should just include other key like matchIdChanged.
Then
// in your state in your constructor add matchId and matchIdChanged then
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.matchId !== prevState.matchId) {
return { matchIdChanged: true, matchId: nextProps.matchId }
}
return null;
}
componentDidUpdate() {
if (this.state.matchIdChanged) {
fetch(matchAPI + this.props.matchId)
.then((matchResponse) => matchResponse.json())
.then((matchfindresponse) => {
console.log(matchfindresponse);
this.setState({
matchData:matchfindresponse,
testTeam:matchfindresponse.team1.name,
matchIdChanged: false // add this
})
})
}
}
instead of using componentDidUpdate() lifecycle hook of react try using getDerivedStateFromProps() lifecycle function if you are using react 16.3, else try using componentWillReceiveProps() for below versions. In my opinion try to avoid the use of componentDidUpdate().
Plus error you are getting is because, setState() function is called, when your component somehow gets unmounted, there can be multiple reasons for this, most prominent being -
check the render function of this component, are you sending null or something, based on certain condition?
check the parent code of component, and see when is the component getting unmounted.
Or you can share these code, so that we might help you with this.
Plus try to debug using ComponentWillUnmount(), put console.log() in it and test it for more clarity.
Hope this helps, thanks

Categories