"Cannot update during an existing state transition" error in React - javascript

I'm trying to do Step 15 of this ReactJS tutorial: React.js Introduction For People Who Know Just Enough jQuery To Get By
The author recommends the following:
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
return (
<div className="alert alert-warning">
<strong>Oops! Too Long:</strong>
</div>
);
} else {
return "";
}
},
render() {
...
{ this.overflowAlert() }
...
}
I tried doing the following (which looks alright to me):
// initialized "warnText" inside "getInitialState"
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
this.setState({ warnText: "Oops! Too Long:" });
} else {
this.setState({ warnText: "" });
}
},
render() {
...
{ this.overflowAlert() }
<div>{this.state.warnText}</div>
...
}
And I received the following error in the console in Chrome Dev Tools:
Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be
a pure function of props and state; constructor side-effects are an
anti-pattern, but can be moved to componentWillMount.
Here's a JSbin demo. Why won't my solution work and what does this error mean?

Your solution does not work because it doesn't make sense logically. The error you receive may be a bit vague, so let me break it down. The first line states:
Cannot update during an existing state transition (such as within render or another component's constructor).
Whenever a React Component's state is updated, the component is rerendered to the DOM. In this case, there's an error because you are attempting to call overflowAlert inside render, which calls setState. That means you are attempting to update state in render which will in then call render and overflowAlert and update state and call render again, etc. leading to an infinite loop. The error is telling you that you are trying to update state as a consequence of updating state in the first place, leading to a loop. This is why this is not allowed.
Instead, take another approach and remember what you're trying to accomplish. Are you attempting to give a warning to the user when they input text? If that's the case, set overflowAlert as an event handler of an input. That way, state will be updated when an input event happens, and the component will be rerendered.

Make sure you are using proper expression. For example, using:
<View onPress={this.props.navigation.navigate('Page1')} />
is different with
<View onPress={ () => this.props.navigation.navigate('Page1')} />
or
<View onPress={ () => {
this.props.navigation.navigate('Page1')
}} />
The two last above are function expression, the first one is not. Make sure you are passing function object to function expression () => {}

Instead of doing any task related to component in render method do it after the update of component
In this case moving from Splash screen to another screen is done only after the componentDidMount method call.
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Button,
Image,
} from 'react-native';
let timeoutid;
export default class Splash extends Component {
static navigationOptions = {
navbarHidden: true,
tabBarHidden: true,
};
constructor(props) {
super(props)
this.state = { navigatenow: false };
}
componentDidMount() {
timeoutid=setTimeout(() => {
this.setState({ navigatenow: true });
}, 5000);
}
componentWillUnmount(){
clearTimeout(timeoutid);
}
componentDidUpdate(){
const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}
}
render() {
//instead of writing this code in render write this code in
componenetDidUdpate method
/* const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}*/
return (
<Image style={{
flex: 1, width: null,
height: null,
resizeMode: 'cover'
}} source={require('./login.png')}>
</Image>
);
}
}

Call the component props at each time as new render activity. Warning occurred while overflow the single render.
instead of
<Item onPress = { props.navigation.toggleDrawer() } />
try like
<Item onPress = {() => props.navigation.toggleDrawer() } />

You can also define the function overflowAlert: function() as a variable like so and it will not be called immediately in render
overflowAlert = ()=>{//.....//}

Related

React Warning: Cannot update a component from inside the function body of a different component

I am using Redux with Class Components in React. Having the below two states in Redux store.
{ spinner: false, refresh: false }
In Parent Components, I have a dispatch function to change this states.
class App extends React.Component {
reloadHandler = () => {
console.log("[App] reloadComponent");
this.props.onShowSpinner();
this.props.onRefresh();
};
render() {
return <Child reloadApp={this.reloadHandler} />;
}
}
In Child Component, I am trying to reload the parent component like below.
class Child extends React.Component {
static getDerivedStateFromProps(props, state) {
if (somecondition) {
// doing some redux store update
props.reloadApp();
}
}
render() {
return <button />;
}
}
I am getting error as below.
Warning: Cannot update a component from inside the function body of a
different component.
How to remove this warning? What I am doing wrong here?
For me I was dispatching to my redux store in a React Hook. I had to dispatch in a useEffect to properly sync with the React render cycle:
export const useOrderbookSubscription = marketId => {
const { data, error, loading } = useSubscription(ORDERBOOK_SUBSCRIPTION, {
variables: {
marketId,
},
})
const formattedData = useMemo(() => {
// DISPATCHING HERE CAUSED THE WARNING
}, [data])
// DISPATCHING HERE CAUSED THE WARNING TOO
// Note: Dispatching to the store has to be done in a useEffect so that React
// can sync the update with the render cycle otherwise it causes the message:
// `Warning: Cannot update a component from inside the function body of a different component.`
useEffect(() => {
orderbookStore.dispatch(setOrderbookData(formattedData))
}, [formattedData])
return { data: formattedData, error, loading }
}
If your code calls a function in a parent component upon a condition being met like this:
const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
const { data, loading, error } = useQuery(QUERY);
if (data && data.users.length === 0) {
return handleNoUsersLoaded();
}
return (
<div>
<p>Users are loaded.</p>
</div>
);
};
Try wrapping the condition in a useEffect:
const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
const { data, loading, error } = useQuery(QUERY);
useEffect(() => {
if (data && data.users.length === 0) {
return handleNoUsersLoaded();
}
}, [data, handleNoUsersLoaded]);
return (
<div>
<p>Users are loaded.</p>
</div>
);
};
It seems that you have latest build of React#16.13.x. You can find more details about it here. It is specified that you should not setState of another component from other component.
from the docs:
It is supported to call setState during render, but only for the same component. If you call setState during a render on a different component, you will now see a warning:
Warning: Cannot update a component from inside the function body of a different component.
This warning will help you find application bugs caused by unintentional state changes. In the rare case that you intentionally want to change the state of another component as a result of rendering, you can wrap the setState call into useEffect.
Coming to the actual question.
I think there is no need of getDerivedStateFromProps in the child component body. If you want to trigger the bound event. Then you can call it via the onClick of the Child component as i can see it is a <button/>.
class Child extends React.Component {
constructor(props){
super(props);
this.updateState = this.updateState.bind(this);
}
updateState() { // call this onClick to trigger the update
if (somecondition) {
// doing some redux store update
this.props.reloadApp();
}
}
render() {
return <button onClick={this.updateState} />;
}
}
Same error but different scenario
tl;dr wrapping state update in setTimeout fixes it.
This scenarios was causing the issue which IMO is a valid use case.
const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
setSomeState(someNewValue);
}).current;
return (
<SomeComponent onSomeUpdate={doUpdate} />
);
fix
const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
setTimeout(() => {
setSomeState(someNewValue);
}, 0);
}).current;
return (
<SomeComponent onSomeUpdate={doUpdate} />
);
In my case I had missed the arrow function ()=>{}
Instead of onDismiss={()=>{/*do something*/}}
I had it as onDismiss={/*do something*/}
I had same issue after upgrading react and react native, i just solved that issue by putting my props.navigation.setOptions to in useEffect. If someone is facing same problen that i had i just want to suggest him put your state changing or whatever inside useEffect
Commented some lines of code, but this issue is solvable :) This warnings occur because you are synchronously calling reloadApp inside other class, defer the call to componentDidMount().
import React from "react";
export default class App extends React.Component {
reloadHandler = () => {
console.log("[App] reloadComponent");
// this.props.onShowSpinner();
// this.props.onRefresh();
};
render() {
return <Child reloadApp={this.reloadHandler} />;
}
}
class Child extends React.Component {
static getDerivedStateFromProps(props, state) {
// if (somecondition) {
// doing some redux store update
props.reloadApp();
// }
}
componentDidMount(props) {
if (props) {
props.reloadApp();
}
}
render() {
return <h1>This is a child.</h1>;
}
}
I got this error using redux to hold swiperIndex with react-native-swiper
Fixed it by putting changeSwiperIndex into a timeout
I got the following for a react native project while calling navigation between screens.
Warning: Cannot update a component from inside the function body of a different component.
I thought it was because I was using TouchableOpacity. This is not an issue of using Pressable, Button, or TouchableOpacity. When I got the error message my code for calling the ChatRoom screen from the home screen was the following:
const HomeScreen = ({navigation}) => {
return (<View> <Button title = {'Chats'} onPress = { navigation.navigate('ChatRoom')} <View>) }
The resulting behavior was that the code gave out that warning and I couldn't go back to the previous HomeScreen and reuse the button to navigate to the ChatRoom. The solution to that was doing the onPress in an inline anonymous function.
onPress{ () => navigation.navigate('ChatRoom')}
instead of the previous
onPress{ navigation.navigate('ChatRoom')}
so now as expected behavior, I can go from Home to ChatRoom and back again with a reusable button.
PS: 1st answer ever in StackOverflow. Still learning community etiquette. Let me know what I can improve in answering better. Thanx
If you want to invoke some function passed as props automatically from child component then best place is componentDidMount lifecycle methods in case of class components or useEffect hooks in case of functional components as at this point component is fully created and also mounted.
I was running into this problem writing a filter component with a few text boxes that allows the user to limit the items in a list within another component. I was tracking my filtered items in Redux state. This solution is essentially that of #Rajnikant; with some sample code.
I received the warning because of following. Note the props.setFilteredItems in the render function.
import {setFilteredItems} from './myActions';
const myFilters = props => {
const [nameFilter, setNameFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
const filterName = record => record.name.startsWith(nameFilter);
const filterCity = record => record.city.startsWith(cityFilter);
const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
props.setFilteredItems(selectedRecords); // <-- Danger! Updates Redux during a render!
return <div>
<input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
<input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
</div>
};
const mapStateToProps = state => ({
records: state.stuff.items,
filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);
When I ran this code with React 16.12.0, I received the warning listed in the topic of this thread in my browser console. Based on the stack trace, the offending line was my props.setFilteredItems invocation within the render function. So I simply enclosed the filter invocations and state change in a useEffect as below.
import {setFilteredItems} from './myActions';
const myFilters = props => {
const [nameFilter, setNameFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
useEffect(() => {
const filterName = record => record.name.startsWith(nameFilter);
const filterCity = record => record.city.startsWith(cityFilter);
const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
props.setFilteredItems(selectedRecords); // <-- OK now; effect runs outside of render.
}, [nameFilter, cityFilter]);
return <div>
<input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
<input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
</div>
};
const mapStateToProps = state => ({
records: state.stuff.items,
filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);
When I first added the useEffect I blew the top off the stack since every invocation of useEffect caused state change. I had to add an array of skipping effects so that the effect only ran when the filter fields themselves changed.
I suggest looking at video below. As the warning in the OP's question suggests, there's a change detection issue with the parent (Parent) attempting to update one child's (Child 2) attribute prematurely as the result of another sibling child's (Child 1) callback to the parent. For me, Child 2 was prematurely/incorrectly calling the passed in Parent callback thus throwing the warning.
Note, this commuincation workflow is only an option. I personally prefer exchange and update of data between components via a shared Redux store. However, sometimes it's overkill. The video suggests a clean alternative where the children are 'dumb' and only converse via props mand callbacks.
Also note, If the callback is invoked on an Child 1 'event' like a button click it'll work since, by then, the children have been updated. No need for timeouts, useEffects, etc. UseState will suffice for this narrow scenario.
Here's the link (thanks Masoud):
https://www.youtube.com/watch?v=Qf68sssXPtM
In react native, if you change the state yourself in the code using a hot-reload I found out I get this error, but using a button to change the state made the error go away.
However wrapping my useEffect content in a :
setTimeout(() => {
//....
}, 0);
Worked even for hot-reloading but I don't want a stupid setTimeout for no reason so I removed it and found out changing it via code works just fine!
I was updating state in multiple child components simultaneously which was causing unexpected behavior. replacing useState with useRef hook worked for me.
Try to use setTimeout,when I call props.showNotification without setTimeout, this error appear, maybe everything run inTime in life circle, UI cannot update.
const showNotifyTimeout = setTimeout(() => {
this.props.showNotification();
clearTimeout(showNotifyTimeout);
}, 100);

React re-render conditional on state change without changing the conditional

I've got a conditional that displays an editor while a certain prop remains true. The thing is, the data with which that editor is rendered with should change every time I select another object with which to populate that editor.
However, because the prop responsible for the conditional rendering doesn't change, even though the data with which the editor is rendered does, it refuses to re-render on state change.
I'm not particularly good at React, so, hopefully someone can explain how I can get around this little hiccup.
Conditional render
{this.state.showEditor ? (<BlockEditor routine={this.state.editorObject} />) : null}
Method that is being called.
handleShowEditor = routine => {
this.setState({ showEditor: true });
this.setState({ editorObject: routine });
};
The editor component
export default class BlockEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
routine: this.props.routine
};
}
render() {
return (
<div>
<Editor
autofocus
holderId="editorjs-container"
onChange={data => this.handleSave(data)}
customTools={{}}
onReady={() => console.log("Start!")}
data={this.props.routine.description}
instanceRef={instance => (this.editorInstance = instance)}
/>
</div>
);
}
}
Is there a reason for setting state separately? Why not
handleShowEditor = routine => {
this.setState({
showEditor: true,
editorObject: routine
});
};
Keep in mind that setState is asynchronous and your implementation could lead to such weird behaviour.
If you are still looking for an answer i have faced the same problem working with the same [Editor.JS][1] :).
This worked for me with functional component:
// on change fires when component re-intialize
onChange={async (e) => {
const newData = await e.saver.save();
setEditorData((prevData) => {
console.log(prevData.blocks);
console.log(newData.blocks);
if (
JSON.stringify(prevData.blocks) === JSON.stringify(newData.blocks)
) {
console.log("no data changed");
return prevData;
} else {
console.log("data changed");
return newData;
}
});
}}
// setting true to re-render when currentPage data change
enableReInitialize={true}
Here we are just checking if data changes assign it to editorData component state and perform re-render else assign prevData as it is which will not cause re-render.
Hope it helps.
Edit:
i am comparing editor data blocks change which is array.
of course you need to perform comparison of blocks more deeply than what i am doing, you can use lodash for example.
[1]: https://github.com/editor-js/awesome-editorjs
As setState is asynchronous you can make another call in its callback.
Try like this
handleShowEditor = routine => {
this.setState({
showEditor: true
}, () =>{
this.setState({
editorObject: routine
)}
});
};

How to avoid using setState in render method?

I am using react-apollo to fetch data through <Query /> and <Mutation />.
Thus, I want to setState when I get some data. I am getting the data in the render method.
Like this:
render() {
return (
<Query query={CAN_UPDATE_POST_QUERY} variables={{ id: this.props.router.query.postId }}>
{ payload => {
if(payload.loading) {
<div style={{width: '98%', textAlign: 'center', maxWidth: '1000px', margin: '50px auto'}}>Loading...</div>
}
if(this.isNew()){
return (
<PleaseSignIn>
{ me => (
...something
) }
</PleaseSignIn>
)
} else if (payload.data && payload.data.canUpdatePost) {
// I get payload here. Here's where I want to set new state.
this.canUpdatePost = payload.data.canUpdatePost
this.setState({ canUpdatePost: this.canUpdatePost })
return (
<PleaseSignIn>
{ me => (
...something
) }
</PleaseSignIn>
)
} else {
return (
<div style={{width: '98%', textAlign: 'center', maxWidth: '1000px', margin: '50px auto'}}>You and your mind seems to be lost. šŸ”</div>
)
}
} }
</Query>
)
}
Using setState in render gives me this error:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
How do I think in React way? And especially, how do I get my state changed when I get payload from react-apollo?
NEWBIE HERE. Sorry if silly.
Thanks in advance. :-)
In general, you should avoid using setState in your render functions. You should avoid having side affects (such as setting state) in your render function and instead should call other component functions to handle data change in your component.
The render() function should be pure, meaning that it does not modify component state, it returns the same result each time itā€™s invoked, and it does not directly interact with the browser.
See the render() method reference in the react docs: https://reactjs.org/docs/react-component.html#render
You can create a function outside of your render method to fix this problem like so:
YourComponent extends React.Component {
handleCanUpdatePost = (canUpdatePos) => {
this.setState({ canUpdatePost })
}
render() {
// Your render logic here
// Whenever you want to setState do so by
// calling this.handleCanUpdatePost(payload.data.canUpdatePost)
}
}
You should also probably have a check to see if the value of state is going to change before setting it to avoid unnecessary re-renders:
handleCanUpdatePost = (canUpdatePos) => {
this.setState((state) => {
if(canUpdatePos !== state.canUpdatePost) {
return {canUpdatePost: payload.data.canUpdatePost}
}
})
}

React setState re-render

First of all, I'm really new into React, so forgive my lack of knowledge about the subject.
As far as I know, when you setState a new value, it renders again the view (or parts of it that needs re-render).
I've got something like this, and I would like to know if it's a good practice or not, how could I solve this kind of issues to improve, etc.
class MyComponent extends ComponentĀ {
constructor(props) {
super(props)
this.state = {
key: value
}
this.functionRender = this.functionRender.bind(this)
this.changeValue = this.changeValue.bind(this)
}
functionRender = () => {
if(someParams !== null) {
return <AnotherComponent param={this.state.key} />
}
else {
return "<span>Loading</span>"
}
}
changeValue = (newValue) => {
this.setState({
key: newValue
})
}
render() {
return (<div>... {this.functionRender()} ... <span onClick={() => this.changeValue(otherValue)}>Click me</span></div>)
}
}
Another component
class AnotherComponent extends Component {
constructor (props) {
super(props)
}
render () {
return (
if (this.props.param === someOptions) {
return <div>Options 1</div>
} else {
return <div>Options 2</div>
}
)
}
}
The intention of the code is that when I click on the span it will change the key of the state, and then the component <AnotherComponent /> should change because of its parameter.
I assured that when I make the setState, on the callback I throw a console log with the new value, and it's setted correctly, but the AnotherComponent doesn't updates, because depending on the param given it shows one thing or another.
Maybe I need to use some lifecycle of the MyComponent?
Edit
I found that the param that AnotherComponent is receiving it does not changes, it's always the same one.
I would suggest that you'll first test it in the parent using a simple console.log on your changeValue function:
changeValue = (newValue) => {
console.log('newValue before', newValue);
this.setState({
key: newValue
}, ()=> console.log('newValue after', this.state.key))
}
setState can accept a callback that will be invoked after the state actually changed (remember that setState is async).
Since we can't see the entire component it's hard to understand what actually goes on there.
I suspect that the newValue parameter is always the same but i can't be sure.
It seems like you're missing the props in AnotherComponent's constructor. it should be:
constructor (props) {
super(props) // here
}
Try replacing the if statement with:
{this.props.param === someOptions? <div>Options 1</div>: <div>Options 2</div>}
also add this function to see if the new props actually get to the component:
componentWillReceiveProps(newProps){
console.log(newProps);
}
and check for the type of param and someOptions since you're (rightfully) using the === comparison.
First, fat arrow ( => ) autobind methods so you do not need to bind it in the constructor, second re-renders occur if you change the key of the component.
Ref: https://reactjs.org/docs/lists-and-keys.html#keys

Updating reactjs state with axios object returned from Alphavantage

I need to pass some stock data to a component named Chart. So far, the Chart component only returns the "test" value placed into state at the top of this code.
Rather than setting state and passing the new data to the Chart component, it gives the error, "TypeError: (intermediate value)(...) is not a function".
I think the getTimeSeries function needs to be defined outside of the render method because the function needs to update state. If so, where does it go?
I think I need to .bind(this), or something with binding, somewhere. I'm not sure.
export default class Main extends React.Component {
state = {
currentUser: null,
instr: null,
instrData: ['test']
};
constructor(props) {
super(props)
}
componentDidMount() {
const { currentUser } = firebase.auth()
this.setState({ currentUser, instr: 'NFLX' });
}
render() {
const { currentUser } = this.state;
var getTimeSeries = function(instr){
if(instr!=null){
const key = 'myKey';
var url = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${instr}&apikey=${key}`;
console.log(url);
axios.get(url)
.then((response) => {
(()=>{ console.log(response.data["Time Series (Daily)"]) })
// THIS IS NOT UPDATING STATE:
(()=>{ this.setState({ instrData: response.data["Time Series (Daily)"] }) })
(()=>{ console.log(this.state) })
})
.catch((error) => console.log(error));
};
}; // end getTimeSeries
return (
<View style={styles.container}>
<Text>
Hi {currentUser && currentUser.email}!
{ getTimeSeries(this.state.instr) }
</Text>
<Chart
instrument={this.state.instr}
instrData={this.state.instrData}
/>
<VotingButtons instrument={this.state.instr} />
</View>
);//end return
}// end render
}//end class
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
})
There's a lot of stuff that's not right here, I'm afraid. Here's a quick runthrough of the big problems:
You're triggering your axios call inside your render() method. A successful axios call will cause state to update (your setState() call), which will then cause another render() call - React components automatically re-render when their state changes. So you're setting up an infinite loop. Probably less dangerous to do this in the componentWillMount() lifecycle method, BUT...
Trying to store AJAX data in component state like this is a bit of an antipattern in React. It's not really supposed to act as a full-on MVC system, it's more like just a View. If you want to do this sort of thing, I strongly recommend using React alongside a robust state management system - Redux is very good and popular.
Your axios promise callbacks are all weird and I don't get what you're trying to do.
axios.get(url)
.then((response) => {
all fine so far, then we get...
(()=>{ console.log(response.data["Time Series (Daily)"]) })
OK, this is what's confusing me. What I expect to see here would be just:
console.log(response.data["Time Series (Daily)"]);
Instead of doing this, you've declared a function that does it. But you haven't called that function, you've just declared it and left it there. And because you haven't assigned it to anything, it's not really doing much.
// THIS IS NOT UPDATING STATE:
(()=>{ this.setState({ instrData: response.data["Time Series (Daily)"] }) })
(()=>{ console.log(this.state) })
These lines will also do nothing as you're not actually executing this.setState() or console.log(), you're declaring functions that will execute them:
foo(); //a call to function foo, no args
() => { foo(); } //a function that calls function foo with no args
Hope this helps!
Big thanks to Duncan Thacker.
Still needs to import the date, but this working code pulls in quotes and builds a candlestick chart:
Chart.js
Api.js

Categories

Resources