I'm creating a recipe book and I trying to get the current recipe page that is opened.
{this.state.recipes.map(res => {
const recipeId = this.state.recipeId;
if (res.id == recipeId) {
this.setState({
selectedRecipe: res
});
}
})}
As you can see, there is a recipes field in state that holds all the recipes, they are fetched from json earlier.
recipeId is the id of the current page that is openend, it is saved before too and has a value of 0 in this case.
Now what I want to do is extract the recipe with id 0 from all the recipes and save it in selectedRecipe, but for some reasons I get the next 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 fix this?
You can use filter method assuming recipes is an array declared in the state.
Example :
const selectedRecipe = this.state.recipes.filter(recipe => recipe.id === this.state.recipeId);
this.setState({ selectedRecipe });
Reorganize your code in this way:
const recipeId = this.state.recipeId
const recipe = this.state.recipes.map(res => res.id === recipeId)
this.setState({ selectedRecipe: recipe })
map isn't the thing to use here.
const selectedRecipe = this.state.recipes.find(recipe => recipe.id === this.state.recipeId);
this.setState({ selectedRecipe });
If you're calling this in render (not onClick or anything like that), you're going to enter a loop, where you're forcing a re-render that sets state, causing a re-render that sets state, causing a re-render....
In this case, you'd be better off looking at componentDidUpdate. If this is being called in an onClick, you should be ok.
Instead of trying to use setState in the render method, you can set the selectedRecipe directly after you get the recipes with the help of the array method find.
Example
componentDidMount() {
getRecipes().then(recipes => {
setState(prevState => {
const { recipeId } = prevState;
const selectedRecipe = recipes.find(res => res.id === recipeId);
return { recipes, selectedRecipe };
});
});
}
Related
I'm very new to react and i'm confused why my state is not updated in another method of mine see example below.
fetchMovies = () => {
const self = this;
axios.get("https://api.themoviedb.org/3/trending/movie/day?api_key=XXXXXXX")
.then(function(response){
console.log(response.data)
self.setState({
collection: response.data.results
})
console.log(self.state.collection)
});
}
makeRow = () => {
console.log(this.state.collection.length);
if(this.state.collection.length !== 0) {
var movieRows = [];
this.state.collection.forEach(function (i) {
movieRows.push(<p>{i.id}</p>);
});
this.setState({
movieRow: movieRows
})
}
}
componentDidMount() {
this.fetchMovies();
this.makeRow();
}
When inside of fetchMovies function i can access collection and it has all the data but this is the part i can't understand in the makeRow function when i console log the state i would of expected the updated state to show here but it doesn't i'm even executing the functions in sequence.
Thanks in advance.
the collection is set after the async call is resolved. Even though makeRow method is called after fetchMoview, coz of async call, u will never know when the call will be resolved and collection state will be set.
There is no need to keep movieRows in the state as that is just needed for rendering. Keeping html mockup in the state is never a good idea.
So u should just call fetchMoviews in the componentDidMount and render the data in as follows:
render() {
const { collection } = this.state;
return (
<>
{
collection.map(c => <p>{c.id}</p>)
}
</>
)
}
make sure the initial value for collection in the state is [] .
The setState() documentation contains the following paragraph:
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.
To access the modified state you need to use the function signature setState(updater, [callback]), so in your case it should be;
self.setState({
collection: response.data.results
}, () => { // Will be executed after state update
console.log(self.state.collection)
// Call your make row function here and remove it from componentDidMount if that is all it does.
self.makeRow()
} )
I am learning ReactJS and trying to update the parent props with the updated state of ingredients from the child component. The setUserIngredients is called and updated ingredients are being passed to parent.
Code :
const [userIngredients, setUserIngredients] = useState([]);
const removeIngredientHandler = id => {
setLoading(true);
fetch(`https://***************.com/ingredients/${id}.json`,{
method:'DELETE'
}).then(response=>{
setLoading(false);
setUserIngredients(prevIngredients =>
prevIngredients.filter(ingredient =>{
return (ingredient.id !== id)
//return ingredient;
})
);
**props.ingredients(userIngredients);**
//userIngredients is still having old value
//need to check on this
}).catch(error => {
setError(error.message);
})
};
The problem is that userIngredients is a variable that is created when the component renders, set to a version fo the state when that component renders. And when you start an asynchronous operation (like a fetch) the callback you pass to that operation will be bound the values from when that callback was created.
The fix here is pretty simple. In the spot where you calculate your new ingredients, simply execute whatever callback you need before returning the value to be stored in the state.
Something like:
fetch(`https://***************.com/ingredients/${id}.json`, {
method: 'DELETE',
}).then(response => {
setLoading(false)
setUserIngredients(prevIngredients => {
// Figure out the new ingredients
const newIngredients = prevIngredients.filter(ingredient => ingredient.id !== id)
// Call your callback with the new ingredients
props.ingredients(newIngredients)
// Return the new ingredients to be stored in your state
return newIngredients
})
})
I have two actions. Sometimes the value returns undefined so instead of receiving the users post I receive all the post. I need to find a better way to get my functions to work together.
Right now I have:
componentDidMount(){
this.props.fetchUser();
let userId = this.props.user.map((user) => (user.id));
console.log(userId)
this.props.fetchPostsFromUser(userId);
}
Because it can't get the userId right away so it won't add the parameter to the end of my user search so it gives me all post.
If I try to do something else like:
componentDidMount(){
this.props.fetchUser();
}
componentWillReceiveProps(){
let userId = this.props.user.map((user) => (user.id));
console.log(userId)
this.props.fetchPostsFromUser(userId);
}
It will loop give me the post but loop infinitly in the console.
Also putting the code below in the render will also make it loop forever.
let userId = this.props.user.map((user) => (user.id));
console.log(userId)
this.props.fetchPostsFromUser(userId);
What is the best way to go about this so it works how I want it to.
EDIT
export const fetchUser = () => dispatch => {
Promise.all([fetch('http://10.6.254.22:5000/userinformation/3')])
.then(([res1]) => {
return Promise.all([res1.json()])
})
.then(([user]) => dispatch ({
// set state in here
type: FETCH_USER,
payload: user
// console.log(res1)
// this.setState({ results: res1, posts: res2 })
// this.setState({ posts: res2 })
}));
}
You want to fetch posts 1 time, but can't do it on mount because you don't have the userId yet. If you do it in componentWillReceiveProps you get an infinite loop -- because fetching posts updates props.
You need to run some code when the component updates, but only when you have a user id and didn't previously have a user id. componentWillReceiveProps is discouraged and deprecated so we will use componentDidUpdate which gets prevProps as it's first paramater.
Using componentDidUpdate:
componentDidUpdate(prevProps) {
let prevUserId = prevProps.user.map((user) => (user.id));
let userId = this.props.user.map((user) => (user.id));
if(!prevUserId && userId) {
this.props.fetchPostsFromUser(userId);
}
}
If you're curious how this might look using hooks, here's an example:
const userId = props.user.map((user) => (user.id))
React.useEffect(() => {
if(userId) {
props.fetchPostsFromUser(userId)
}
}, [userId])
The hook will only be called when userId has changed value, and will only fetch posts if there is a userId
I'm creating a react-native app and I need one of my components to use a axios get request when I do an action on another component. But the problem is that my component that I need an axios get request from is not being passed any props and the current state and new state is an array of 20+ objects with each at least 10 key value pairs. So I would need a component did update with a good if statement to not go into an infinite loop. I can't do an if statement with prevState to compare with current state because there is only a minor change happening in state. So I need to know how to stop the component Did Update from having an infinite loop.
state = {
favouriteData: []
}
componentDidMount () {
this.getFavouriteData()
}
componentDidUpdate (prevProps, prevState) {
if (this.state.favouriteData !== prevState.favouriteData){
this.getFavouriteData()
}
}
getFavouriteData = () => {
axios.get('http://5f46425d.ngrok.io')`enter code here`
.then(response => {
const data = response.data.filter(item => item.favourite === true)
this.setState({
favouriteData: data
})
})
}
The issue is that you are trying to compare 2 object references by doing the following. It will always return since the references are always different.
if (this.state.favouriteData !== prevState.favouriteData) {...}
To make life easier, we can use Lodash(_.isEqual) to deal with deep comparison of objects.
state = {
favouriteData: []
}
componentDidMount () {
this.getFavouriteData()
}
componentDidUpdate (prevProps, prevState) {
this.getFavouriteData(prevState.favouriteData)
}
getFavouriteData = (prevData) => {
axios.get('http://5f46425d.ngrok.io')
.then(response => {
const data = response.data.filter(item => item.favourite === true);
// compare favouriteData and make setState conditional
if (!prevState || !_.isEqual(prevData, data)) {
this.setState({
favouriteData: data
})
}
})
}
You should use react-redux to avoid this kind of issues. Assuming you are not using flux architecture, you can pass this.getFavouriteData() as props to the other component like:
<YourComponent triggerFavouriteData = {this.getFavouriteData}/>
As we've known, setState is async. I've read few questions about setState, on how to use the value right after setState, but those aren't what I need right now.
I'm trying to set value for array List, and then use that List to do a function to get the value for Result. If setState isn't async, then it would be like this
`
handleChange(e) {
const resultList = this.state.list.slice();
resultList[e.target.id] = e.target.value;
this.setState({
list: resultList,
result: this.doSomething(resultList) // this.doSomething(this.state.list)
});
}
`
Is there anyway to achieve this? A documentation or keyword to research would be awesome.
Many thanks
There is a callback parameter to setState which is called after the state has been updated
this.setState({
list: resultList,
result: this.doSomething(resultList)
}, () => {
//do something with the updated this.state
});
You can use async await like
async handleChange(e) {
const resultList = this.state.list.slice();
resultList[e.target.id] = e.target.value;
this.setState({
list: resultList,
result: await this.doSomething(resultList) // this.doSomething(this.state.list)
});
}
The use of this.state together with this.setState is discouraged, exactly because state updates are asynchronous and may result in race conditions.
In case updated state derives from previous state, state updater function should be used. Because it's asynchronous, event object should be treated beforehand, due to how synthetic events work in React:
const { id, value } = e.target;
this.setState(state => {
const list = [...state.list];
list[id] = value;
return {
list,
result: this.doSomething(list)
};
});