I'm attempting to render a page of Pokemon with their attributes (when selected) in react-redux.
I have a PokemonDetail component
export function pokemonDetail (props) {
const { id } = useParams()
const moves = Object.values(props.state.entities.moves).map((val) => val.name)
const thisPokemon = props.state.entities.pokemon[id]
useEffect(() => {
props.requestSinglePokemon(id)
}, [id])
return(
<section className="pokemon-detail">
<ul>
<figure>
<img src={thisPokemon.imageUrl} alt={thisPokemon.name} />
</figure>
<li><h2>{thisPokemon.name}</h2></li>
<li>Type: {thisPokemon.pokeType}</li>
<li>Attack: {thisPokemon.attack}</li>
<li>Defense: {thisPokemon.defense}</li>
<li>Moves: {moves.join(', ')}</li>
</ul>
</section>
)
}
This component makes a request to grab a Pokemon via a thunk action
export const requestSinglePokemon = (id) => (dispatch) => {
APIUtil.fetchPokemon(id).then(
(payload) => (dispatch(receiveOnePokemon(payload)))
)
}
The problem is, the moves object will be blank {} until the request completes, where it will then be populated with the Pokemon, moves, and items. When it tries to initially render, then, it will throw an error that it's trying methods on a blank object. An easy fix to this is to simply write if (thisPokemon)... { but I'm wondering if there's a way to make my constants wait for the request to complete before being defined.
Without seeing how the props interact with each other or the exact implementation of the requestSinglePokemon, I can only give you a workaround where you define a local variable that reacts to thisPokemon and use that to conditional render. Basically what you are doing with the if (thisPokemon) code, but easier to read
Basically, you could define a loading state and render a loader accordingly to the truthy-iness of thisPokemon.
E.g.
// simplified example
// ... all of your imports
import {useMemo} from 'react'
function PokemonDetail () {
// ... all of your code
// when thisPokemon is falsy, loaded is false
// when thisPokemon is truthy, loaded is true
const loaded = useMemo(() => {
return Boolean(thisPokemon);
}, [thisPokemon])
return <>
{!loaded&& {'Data is loading...'}}
{loaded && <>{/* your JSX */}</>}
</>
}
A better solution is to simply modify the parent props and conditional the component as a whole, so the loading logic is done at the container instead of the component. This way, the PokemonDetail component does not have to care about undefined thisPokemon conditions.
i.e. Do the conditional rendering at the parent component
I am having an issue with my React app. I am trying to set the state of the parent component based on the child component's value. I can see in the dev tools and log window that the child's value is being received by the parent; however, the setState is not working as it should. I have tried creating a separate function just to set the values; hoping for it to act as a middleware but no luck.
I have been through about a couple of StackOverflow threads but not many cater for functional components. I found the following codegrepper snippet for reference but it does not help either.
link: https://www.codegrepper.com/code-examples/javascript/react+function+component+state
Most of the threads deal with how to get the value to the parent component; however, my issue is more "setting the state" specific.
import React, { useEffect, useState } from "react";
import Character from "../component/Character";
import Filter from "../component/Filter";
import Pagination from "../component/Pagination";
import axios from "axios";
import "./Home.css";
const Home = (props) => {
const [API, setAPI] = useState(`https://someapi.com/api/character/?gender=&status=&name=`);
const [characterData, setCharacterData] = useState([]);
const [pagination, setPagination] = useState(0);
const makeNetworkRequest = (data) => {
setAPI(data);
setTimeout(() => {
axios.get(data).then(resp => {
setPagination(resp.data.info)
setCharacterData(resp.data.results)
})
}, 1000)
}
const handleFormCallBack = (childData) => {
setAPI(childData);
makeNetworkRequest(API);
console.log(`Parent handler data ${childData}`)
console.log(`Parent handler API ${API}`)
}
useEffect(() => {
makeNetworkRequest(API)
}, [characterData.length]);
const mappedCharacters = characterData.length > 0 ? characterData.map((character) => <Character key={character.id} id={character.id} alive={character.status} /* status={<DeadOrAlive deadoralive={character.status} /> }*/ gender={character.gender} name={character.name} image={character.image} />) : <h4>Loading...</h4>
return (
<div className="home-container">
<h3>Home</h3>
<Filter parentCallBack={handleFormCallBack} />
<div className="characters-container">
{mappedCharacters}
</div>
{/* <Pagination pages={pagination.pages}/> */}
</div>
)
}
export default Home;
In the code above I am using a callback function on the parent named "handleFormCallBack", mentioned again below to get the information from the child filter component. When I log the value, the following results are being generated.
const handleFormCallBack = (childData) => {
setAPI(childData);
makeNetworkRequest(API);
console.log(`Parent handler data ${childData}`)
// Parent handler data https://someapi.com/api/character/?gender=&status=&name=charactername
console.log(`Parent handler API ${API}`)
// Parent handler API https://someapi.com/api/character/?gender=&status=&name=
}
I am not sure what I am doing wrong but any sort of help would be much appreciated.
Kind Regards
useState works pretty much like setState and it is not synchronous, so when you set the new value using setAPI(childData); react is still changing the state and before it actually does so both of your console.log() statements are being executed.
Solution - after setting the new value you need to track if it has changed, so use a useEffect hook for the endpoint url and then when it changes do what you want.
useEffect(() =< {
// do anything you want to here when the API value changes. you can also add if conditions inside here.
}, [API])
Just to check what I have explained, after calling setAPI(childData); add a setTimeout like
setTimeout(() => {
// you will get new values here. this is just to make my point clear
console.log(Parent handler data ${childData})
console.log(Parent handler API ${API})
}, 5000);
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 component that makes a request and displays a list of jobs.
import React, { useState, useEffect, Fragment } from 'react';
import { Jobs } from '../components/Jobs.component';
export const Fixed = () => {
const [jobs, setJobs] = useState([]);
useEffect(() => {
getItems();
}, []);
async function getItems() {
const url = 'http://localhost:8081/api/fixed/list';
const res = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
});
const data = await res.json();
console.log(data);
setJobs(data.jobsList);
console.log(jobs);
}
return (
<Fragment>
{jobs.map(job => (
<div>
<Jobs job={job} />
</div>
))}
</Fragment>
);
};
My problem is that the first console outputs an array of jobs, but the second console displays an empty array. And an empty value is passed to the job component, which causes an error.
He does not have time to write the work in a state? Where am I wrong?
Method setJobs needs some time to change state so console.log runs faster than value changes.
You should render list if the array length is bigger than 0.
{jobs.length && jobs.map(job => <Jobs job={job} />)}
State updates are run asynchroniously
The reason your console.log shows an empty array is because setJobs runs asynchroniously and will update jobs value on next render. Looking at react setState documentation (same as useState react hooks) :
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state.
And so
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.
const ... jobs ... is a constant - it will be a different constant in 2 different renders, but it will not change value during a single render
The jobs inside getItems is a closure and will reference to the value from the first render, while setJobs will only change the value in second render.
It's similar to the following:
const rememberJobs = (jobs) => () => console.log(jobs)
const first = rememberJobs([])
const second = rememberJobs([1, 2, 3])
first()
second()
I am trying out the new React Hooks, and I am a little stuck as the UI is not updating when the local state is updated. Here is my code,
import React, { useState, useEffect } from 'react';
import Post from './Post'
import PostForm from './PostForm';
import axios from 'axios';
function PostsList() {
const [posts, setPosts] = useState([]);
// setting up the local state using useEffect as an alternative to CDM
useEffect(() => {
axios.get('...')
.then(res => {
// the resposne is an array of objects
setPosts(res.data)
})
})
const handleSubmit = (data) => {
// the data I am getting here is an object with an identical format to the objects in the posts array
axios.post('...', data)
.then(res => {
// logging the data to validate its format. works fine so far..
console.log(res.data);
// the issue is down here
setPosts([
...posts,
res.data
])
})
.catch(err => console.log(err))
}
return (
<div>
<PostForm handleSubmit={handleSubmit} />
<h3>current posts</h3>
{ posts.map(post => (
<Post key={post.id} post={post} />
)) }
</div>
)
}
when I submit the form, the UI flickers for a split second and then renders the current state without the new update, it seems that something is preventing it from re-rendering the new state.
If more code/clarification is needed please leave a comment below.
thanks in advance.
alright, problem solved with the helpful hint from #skyboyer,
so what happened initially is, the useEffect() acts like componentDidMount() & componentDidUpdate() at the same time, that means whenever there is an update to the state, the useEffect() gets invoked, which means resetting the state with the initial data coming from the server.
to fix the issue I needed to make the useEffect() renders the component only one time when it's created/rendered as opposed to rendering it every time there is an update to the state. and this is done by adding an empty array as a second argument to the useEffect() function. as shown below.
useEffect(() => {
axios.get('...')
.then(res => {
setPosts(res.data)
})
}, [])
thanks :)