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);
I have a function that renders content to page based on a state populated by API data, but I need to have an onClick event to refine that content;
So currently getPosts returns information from the state 'posts' which is provided with data from our API, but i want to filter this content further, so my idea is to have some sort of event listener, and if actioned, change the data coming out of getPosts.
constructor() {
super();
this.state = {
posts: ""
}
this.getPosts = this.getPosts.bind(this);
}
async componentWillMount(){
var data = await api.posts();
this.setState({posts: data.data});
console.log(this.state.posts);
}
getPosts(type){
if(this.state.posts.length){
return this.state.posts.map((content,index) => {
var url = content.Title.replace(/[^\w\s]/gi, '');
url = url.replace(/\s+/g, '-').toLowerCase();
if(type === content.PostType){
//output something different
}
else{
return(
<Col md={4} className="mb-4" key={index}>
{content.title}
</Col>
);
}
})
}
}
render() {
return (
<div>
<p><button onClick={()=>{this.getPosts('blog')}}>blog</button> <button onClick={()=>{this.getPosts('news')}}>news</button></p>
{this.getPosts()}
</div>
)
}
So my getPosts works fine without any type, how to do tell it to re-output the function on the page based in the onClick event?
Without getting into the complexities of context and keys, a component requires a change in props or state to re-render. To read more about state and component life-cycle, the docs have a great explanation for class components.
Your component does not re-render after the onClick event handler's call to getPosts because getPosts does not update internal component state. getPosts works within render because those values are being returned to React. By using getPosts as an onClick event handler, you are creating React elements and trying to return them to the window.
What follows should be treated as psuedo code that shows how to trigger your component to render different posts:
Consider adding another key to state in your constructor,
constructor(props) {
super(props);
this.state = {
posts: "",
type: null
};
this.getPosts = this.getPosts.bind(this);
this.onClick = this.onClick.bind(this);
}
and creating a click handler that doesn't try to return React elements
function onClick(evt) {
this.setState({ type: evt.target.value });
}
and values to your buttons
<button onClick={this.onClick} type="button" value="blog">blog</button>
Now your button will update state with your new post type, causing your component to re-render:
render() {
return (
<div>
<p>
<button onClick={this.onClick} type="button" value="blog">blog</button>
<button onClick={this.onClick} type="button" value="news">news</button>
</p>
{this.getPosts()}
</div>
);
}
With the content type being stored in state, you can now implement your getPosts call in any way that works for you. Good luck!
It strays from the question asked, but it is worth noting componentWillMount is being deprecated, and componentDidMount is a preferable life-cycle function for side-effects and asynchronous behavior. Thankfully, the documentation has lots of details!
Ok so you should start by changing your default this.state to
this.state = {
posts: []
}
remember that you want to iterate over an array of data instead of iterate a string, that will throw an error if you do that, so better keep from the beginning the data type you want to use.
Then you need to separate the responsibility for your getPosts method, maybe getPostByType is a better name for that, so you have
getPostByType(type) {
// if type is same as content.PostType then return it;
const nextPosts = this.state.posts.filter((content) => type === content.PostType);
this.setState({ posts: nextPosts });
}
and finally you can iterate over posts, like this
render() {
// better use content.id if exists instead of index, this is for avoid problems
// with rerender when same index key is applied.
const posts = this.state.posts.map((content, index) => (
<Col md={4} className="mb-4" key={content.id || index}>
{content.title}
</Col>
));
return (
<div>
<button onClick={() => this.getPostByType('blog')}>Show Blog Posts</button>
{posts}
</div>
);
}
Then you can use getPostsByType any time in any click event passing the type that you want to render.
So i'm attempting to generate a random selection from an array which a user can add to by pressing a button and i'm a bit confused on how to do it.The app is made in react and it's used movieDB's api to make a film search engine. This is what I attempted so far:
import React from 'react';
import App from './App';
import{ Button } from 'reactstrap'
import { AddToSelector } from './Components/AddToSelector.Component'
class MovieRow extends React.Component{
addToFavourites = () =>
{
var arr = [];
const movieRow = <AddToSelector key={this.props.movie.id}
movie={this.props.movie}/>
console.log(this.props.movie.title);
arr = arr.push(this.props.movie.id);
}
pickRandom = (arr) =>
{
console.log(arr);
}
render() {
return (
<div>
<table key = {this.props.movie.id}>
<tbody>
<tr>
<td>
<img alt="poster" src={this.props.movie.poster_src}/>
</td>
<td>
<p>{this.props.movie.title}</p>
<p>{this.props.movie.overview}</p>
<Button color="primary" onClick={this.addToFavourites}>Add</Button>
<Button color="primary" onClick={this.pickRandom}>Random</Button>
</td>
</tr>
</tbody>
</table>
</div>
);
}
}
export default MovieRow;
You're getting there. You've got a few issues:
You're defining arr in addToFavourites, so every time it is called a new arr is created that's only available within that function. Essentially, you're creating the array, adding the item, and then it's tossed in the garbage.
Since your MovieRow represents one movie, you're gonna want to have
a wrapper component that maintains arr as part of its state. You'll
also define addToFavourites there and then pass that down to each
MovieRow component, making sure to pass at least the movie id so you
know which movie to add to the favorites
I'm not sure what movieRow in addToFavorites does because you assign it and then never use it. Essentially here you're defining movieRow on the global window object the first time around and then reassigning that each time you click. This seems unnecessary.
You'll also want to move pickRandom to the parent component that maintains your favorite list.
Hopefully this gets you going in the right direction. Let me know if you have questions
I think state is what you're probably looking for. You need to set initial array state in constructor, then update it in addToFavourites (using this.setState) and use updated state in pickRandom. Please consult the docs.
You should store the list of movies in you component state. Read more about the app state and components lifecycle in the React documentation
class MovieRow extends React.Component {
constructor(props) {
this.state = {
movies: []
}
}
}
Then in the method that is handling the click event in your Add button, you can use setState() to update the state and then changes will be reflected in the template.
addToFavourites() {
const newMovie = getMovie(); // I don't know where are you getting the movie, but you get the idea.
this.setState((state) => {
return {
movies: [...state.movies, newMovie]
}
});
}
Then considering you have stored in the state an array of objects representing your movies. You should map that array to a list of components in the render function.
Example:
render() {
return (
this.state.movies.map((movie) => <MovieComponent key={movie.id} title={movie.title} />)
)
}
I'm very new to React and although made some progress that I'm happy with, I've hit a snag that I'm stuck with and have been for over a day.
I'm creating a small app that uses React at the frontend and a .NET Core API server-side to supply the data.
So far, the following code works:
import React, { Component } from 'react';
export class LGSRValidation extends Component {
displayName = LGSRValidation.name
constructor(props) {
super(props);
this.state = { lgsr: [], loading: true };
fetch('api/FormValidation/LGSR')
.then(response => response.json())
.then(data => {
this.setState({
lgsr: data,
loading: false
});
});
}
static renderLGSRTable(lgsr) {
return (
<table className='table'>
<thead>
<tr>
<th>Job Number</th>
<th>Job Type</th>
<th>UPRN</th>
<th>Gas Register Number</th>
<th>Service Date</th>
</tr>
</thead>
<tbody>
<tr key={lgsr.jobnumber}>
<td>{lgsr.jobNumber}</td>
<td>{lgsr.jobType}</td>
<td>{lgsr.uprn}</td>
<td>{lgsr.gasRegisterNumber}</td>
<td>{lgsr.serviceDate}</td>
</tr>
</tbody>
</table>
);
}
render() {
let contents = this.state.loading
? <p><em>Loading...</em></p>
: LGSRValidation.renderLGSRTable(this.state.lgsr);
return (
<div>
<div>
<h1>{this.displayName}</h1>
<p>This component demonstrates fetching data from the server.</p>
{contents}
</div>
</div>
);
}
}
What it quite simply does is call my API to return a single object to the client-side code and render each property within the object into a separate table column.
What I'm trying to do is introduce a button that onClick will call the API again, pull a new object and change the values in the table for the new property values. I'm happy with my server-side code and the creation of the random objects but my client side code is just not working.
I'm introducing the button within the parent tags of the component like this:
render() {
let contents = this.state.loading
? <p><em>Loading...</em></p>
: LGSRValidation.renderLGSRTable(this.state.lgsr);
return (
<div>
<div>
<h1>{this.displayName}</h1>
<p>This component demonstrates fetching data from the server.</p>
{contents}
</div>
<button onClick={this.getData}>
Next
</button>
</div>
);
}
Which seems to render fine. I'm also introducing a getData function like this further up:
getData() {
fetch('api/FormValidation/LGSR')
.then(response => response.json())
.then(data => {
this.setState({
lgsr: data,
loading: false
});
});
this.setState({
contents : this.state.lgsr
});
}
I suspect it's at this part where I'm going wrong.
Please can you advise how I can click my new button to call the API, return some data and pass the properties of the lgsr object into the corresponding table columns; replacing the previous data with the new data.
You need to bind your getData() to your constructor so you can use this for your component rather than the current function.
this.getData = this.getData.bind(this);
A quicker way is to use arrow function like the following...
getData = () => {....}
This should hopefully fix your problem and allow you to use this.state
I see 4 ways to improve your code:
Load data in the componentDidMount lifecycle method instead of the component's constructor (more info). This should not be related to you problem but it's safer to ensure the component did mount before you possibly set the state.
As SGhaleb mentioned be sure that your getData function is bound, either using an arrow function or the bind alternative, otherwise this.getData is undefined and the button's onClick should be a no op. You could use getData for your initial request to not have duplicated code in your class.
In getData you set the state's content property, but you do it outside of the request's callback. It's value may be either the new or the old lgsr object of the new request. Check that you do want this or move it to the fetch callback.
Use a dedicated component to render the LGSR table instead of using a static render method and pass the required data via props to it. Again, nothing to do with your question.
What are you trying to achieve here?
Can you paste your final code?
I see something terrible here. Your 'loading' is already declared in your component state and you're updating 'contents' state with the lgsr data. And in your render, you have declared contents as this.state.loading
constructor(props) {
super(props);
this.state = { lgsr: [], loading: true };
/*some code here*/
getData () {
fetch('api/FormValidation/LGSR')
.then(response => response.json())
.then(data => {
this.setState({
lgsr: data,
loading: false
});
});
this.setState({
contents : this.state.lgsr
});
}
render() {
let contents = this.state.loading
I am using map along with Table, however I cannot get it to work as this is my first ReactJS application.
Here is What I am doing:
<table className="table table-striped">
<tbody>
{this.state.itemList.map(this.addMemberRow)}
</tbody>
</table>
Here is my addMemberRow funtion, I am not sure if it is correct!
addMemberRow(item, i) {
return(
<tr key={i}>
<td> {item} </td>
</tr>
);
}
Here is my constructor
constructor(props) {
super(props);
this.state = {
itemList: [],
};
this.addMemberRow = this.addMemberRow.bind(this);
this.openAll = this.openAll.bind(this);
}
This above code is from child component of
In the App I have
this.setState({itemList: itemList});
So whenever I add something to the array, I call setState so that this child component re-renders. but the data never appears. Nor does anything else even If I put
<h1>bhavya</h1>
in place of
{item}
EDIT :
onUpdate(item){
itemList.push(item);
this.setState({itemList: itemList}, () => {
alert(that.state.itemList.length);
});
}
This is where I set the state. I get the alert everytime with updated length.
EDIT 2 :
my array is empty in the child component ! :( I did console.dir(this.state.itemList) as soon as I enter render in child component. It shows nothing in the console, but in browser console It shows Array which is empty!! :(
I pass the itemList
<AddList itemList={this.state.itemList}/>
and I also have
App.defaultProps = {
itemList: []
};
and
constructor(props) {
super(props);
this.state = {
itemList: [],
};
this.onUpdate = this.onUpdate.bind(this);
}
You are setting the itemList state in App, and rendering itemList from state in AddList - a completely different component.
Thats not how state works. Only one component owns any particular state. If you are doing the update on the itemList state in your App component, App needs to pass the itemList down to the child component, and the child component needs to render from props -- not state. State is not shared between components. It is owned by one single component and then passed down for rendering through props.
Here is an example:
App's render method body:
return <AddList itemList={this.state.itemList} />
The line above passes App's itemList state down to the AddList component as a prop.
AddList's render method body (notice use of props):
render() {
var itemList = this.props.itemList;
return (
<table className="table table-striped">
<tbody>
{itemList && itemList.map(this.addMemberRow)}
</tbody>
</table>
);
}
Here - AddList renders the itemList array that was passed down as a prop.
I believe that this code:
onUpdate(item){
itemList.push(item);
this.setState({itemList: itemList});
alert(that.state.itemList.length);
}
should be more like this:
onUpdate(item){
var itemList = this.state.itemList;
itemList.push(item);
this.setState({itemList: itemList}, () => {
alert(this.state.itemList.length);
});
}
setState in React is not guaranteed to be synchronous; that is, you can't count on the state object to be set immediate after setState returns, which is why there's an optional callback argument to setState so you can be notified when all the state changes have taken effect.