How come my state is not updated after fetching data? - reactjs

Good morning everyone,
I just finished my training in React and I wanted to make a project to practice.
To start, i'm juste posting data in a firebase database.
Then, i would like to fetch data to display it in a react component using hooks (useState and useEffect).
The problem is i end up in the classic trap of infinite loop after fetching datas with axios, but i can't figure out how to stop it with dependency.
Please find code below :
import React, {useState} from 'react'
import {useEffect} from 'react'
import classes from './Incomes.module.css'
import axios from '../../../axios'
import Button from '../../UI/Button/Button'
import Input from '../../UI/Input/Input'
const Incomes = (props) => {
const [inputValue, setInputValue] = useState('');
const [fetchedTypes, setFetchedTypes] = useState([]);
const onClickHandler = () => {
const type = {type: inputValue}
axios.post('/types.json', type)
.then(response => {
alert('ok c good');
})
.catch(error => console.log(error))
}
const onChangeHandler = (event) => {
setInputValue(event.target.value)
}
useEffect(() => {
axios.get('/types.json')
.then(response => {
let types = []
for(let key in response.data) {
types.push({...response.data[key], id: key})
}
console.log('types : ', types)
setFetchedTypes(prevFetchedTypes => [...prevFetchedTypes, types])
})
.catch(error => console.log(error))
console.log('fetchedtypes', fetchedTypes)
}, [fetchedTypes])
return (
<div className={classes.Incomes}>
<h2>Paramètres des entrées</h2>
<h3>Ajouter un type de revenu</h3>
<Input type="text" disabled={false} change={onChangeHandler} placeholder="Nom du champ"></Input>
<Button onClick={onClickHandler} type="Normal" clicked={onClickHandler}>Ajouter</Button>
<h3>Liste des types de revenus disponibles</h3>
</div>
)
}
export default Incomes;
When i console log types, the datas are correct.
But when i console log fetchedTypes, it's an empty array.
I found this post but it's not working neither :
React useEffect infinite loop fetch data axios
I'm not sure if it's an issue with the way I use useEffect or the way I update state, or both.
Thank you for your help
Have a good day

The reason you're getting an infinite loop is because of your useEffect and for two reasons.
I'm assuming fetchedTypes is not a primitive but some object/array so it'll always be different every time. Don't use it with useEffect. If you want to depend on objects, go for useDeepCompareEffect by Kent but you don't need it here.
The second and main reason for your infinite loop is the statement:
setFetchedTypes(prevFetchedTypes => [...prevFetchedTypes, types])
Your useEffect is updating fetchedTypes with the above line but it's also meant to run when fetchedTypes changes. So useEffect updates it and since it's a dependency which has changed, useEffect runs again resulting to an infinite loop.
A possible solution:
If you want to fetch data when your component loads, change your useEffect to this:
useEffect(() => {
axios.get('/types.json')
.then(response => {
let types = []
for(let key in response.data) {
types.push({...response.data[key], id: key})
}
console.log('types : ', types)
setFetchedTypes(types)
})
.catch(error => console.log(error))
console.log('fetchedtypes', fetchedTypes)
}, [])
Ignore any lint warning. An empty dependency array means this will run only once when the component renders.
But since you have an on click handler, I'm assuming you want to update the data after the POST request. If your onClickHandler returns the newly created type then do this:
const onClickHandler = () => {
const type = {type: inputValue}
axios.post('/types.json', type)
.then(response => {
const newType = response.data;
const updatedTypes = [...types, newType]
setFetchedTypes(updatedTypes)
})
.catch(error => console.log(error))
}
NOTE: I don't know the structure of your types so I'm working with assumptions but that's the idea

Related

How dose the useEffect and useState hook works?

Hi I am learning React Hooks I know useEffect when used without any dependency array should run after every render but what about render itself does it happen every time a state changes because I am trying following code where I am rending a static h1 to the DOM and in my useEffect I am using a fetch to call a json file on the success of the fetch I am setting a state loaded to true from false I have written this example for the learning purpose only I was expecting that this should have caused a infinite loop because useEffect without dependency array should run after every render and I am calling set state which according to me should force the render to happen but It only run the useEffect twice but
when I am using the other example where I am using useState create a name state and in the useeffect without dependency arry I am changing the name state with Math.random it is behaving as expected causing infinite loop what I understand is useState does not cause render state to happen untill the value of the state changed as in the first example the state value only changed once from false to true and in name example it is random so changes every time
Just need a to know is my understanding correct and also if you can share where I can learn these Kind of stuff as I did not find this on the react doc.
example 1 with fetch
`
const App = () => {
// const [name, setName] = useState('sumit');
const [loaded, setLoaded] =useState(false);
useEffect(() => {
console.log('sumit');
fetch('./option.json').then((res) => {
console.log(res);
return res.json();
}).then((res) => {
console.log(res);
setLoaded(true);
})
// setName(Math.random());
})
return (
<h1>sumit</h1>
);
}
example 2 with name
const App = () => {
const [name, setName] = useState('sumit');
// const [loaded, setLoaded] =useState(false);
useEffect(() => {
console.log('sumit');
/* fetch('./option.json').then((res) => {
console.log(res);
return res.json();
}).then((res) => {
console.log(res);
setLoaded(true);
}) */
// setcount(count+1);
setName(Math.random());
})
return (
<h1>sumit</h1>
);
}
`

After useEffect API call, state set by useState for json data being passed to a component as props returns empty array

I'm still a beginner in React and I'm trying to use useEffect to fetch data from an API and then useState to set the state and then pass that state as props to a child component.
But in my child component, it appears as an empty array each time when I do console.log. I understand that on the first render the state of my initial state is an empty array []. But I've been trying to combat this and send the right JSON data but can't seem to do so.
I am trying to do this as I have multiple child components that I wanna send data to.
Below is a workaround I coded up with some digging around but doesn't work either:
const api = 'url string'
const [races, setRaces] = useState([]);
const [races2, setRaces2] = useState([races]);
useEffect(() => {
fetch(api)
.then((resp) => resp.json())
.then((response) => setRaces(response));
}, []);
useEffect(() => {
if (races.length) setRaces2(races);
}, [races]);
<Child data={races2}
But this does not seem work to work either when I do console.log(props.data) in the child component.
This is how normally one would fetch data and try and send the data but in both cases, it's been the same.
const api = 'url string'
const [races, setRaces] = useState([]);
useEffect(() => {
fetch(api)
.then((resp) => resp.json())
.then((response) => setRaces(response));
}, []);
<Child data={races}
Following is a rough flow diagram explaining what I wanna do:
Thank you for your help in advance.
I made this quick example.
Here is what the code does:
Fetching the Data using UseEffect
Storing into State
Passing the State into Component as Props
Fetching the Props and Displaying the data.
Code for App.js
import "./styles.css";
import ChildComponent from "./ChildComponent";
import { useEffect, useState } from "react";
export default function App() {
const [title, setTitle] = useState(null);
// * Init on Page Load
useEffect(() => {
fetchTitle();
}, []);
const fetchTitle = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts/1"
);
const data = await response.json();
setTitle(data.title); //Setting the response into state
};
return (
<div className="App">
<ChildComponent data={title} />
</div>
);
}
Code for ChildComponent.js
export default function ChildComponent({ data }) {
return <div>{data}</div>;
}
I created this Codesandbox. This might help.
https://codesandbox.io/s/elegant-lumiere-cg66nt
Array and object are referential data types, passing as array dependency will not re-run side effect. useEffect dependencies should be primitive data type (string, number, boolean,undefined or null).
useEffect(() => {
if (races.length) setRaces2(races);
}, [races.length])// Dependencies must be primitive data type not referencial.

Usestate not updating

Pretty new to React Hooks and I'm running into a problem with a third party api. I'm getting the data but it's the useState hook isn't updating my state value. I'm pretty sure this is the problem bc I'm getting an error that items.map isn't a function. It does this bc there's nothing in items??? Anyone know how to deal with this?
import React, { useState, useEffect} from "react";
import axios from "axios";
const FeaturedWorks = () => {
const [items, setItems] = useState([]);
const fetchRandomData = async () => {
try {
const res = await axios(
`https://www.rijksmuseum.nl/api/en/collection?key=XXXXXXX`
);
setItems(res.data.artObjects);
console.log(res);
console.log(items);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchRandomData();
},[]);
return (
<div className="featured-container">
{items.map((item, idX) => (
<h5 key={idX}>{item.title}</h5>
))}
</div>
);
};
export default FeaturedWorks;
Here's a screenshot of my response:
Since you're passing [] as the initial state for items, it's already an (empty) array on the first render. If items is not updated, items.map will still work, since .map still exists for an empty array.
So, my guess is that your setItems is indeed updating the state with the result from your query, but res.data is not an array. If res.data is and object like { values: ['foo', 'bar'] }, instead of an array like ['foo', 'bar'], then items will be set to this object and items.map() will in fact throw an error since the object must be an array for the .map function to be defined.
Does you console.log(res.data); really logs an array, or does it log something different?

React hooks async problems: map is executing before data is returned - solutions?

In the code below, you can see that I'm mapping over data returned from an axios GET request. I'm then passing this data through a filter, and setting it to state (under the variable gig).
I'm then wanting to map over the data held in gig - only problem is that when I try, I get an error say that TypeError: gig.map is not a function, and gig console logs to undefined.
However, when gig is console logged inside the useEffect method, it returns the data I want.
So I'm guessing that what is happening is that setState is aysnc, and the gig.map function is being reached before the gig has been set to filteredGigs.
Any suggestions on how to fix this?
Here's the full code:
import React, {useState, useEffect} from 'react'
import axios from 'axios'
import { auth } from 'firebase/app'
const UniqueVenueListing = (props) => {
const [gig, setGig] = useState([])
const authUserId = props.userDetails.uid
useEffect(()=>{
axios.get("https://us-central1-gig-fort.cloudfunctions.net/api/getGigListings")
.then(res => {
let filteredGigs = res.data
.filter(gig => {
return gig.user !== authUserId
})
setGig({gig: filteredGigs})
console.log(gig)
})
},[])
useEffect(() => {
console.log(gig)
}, [gig])
return(
<div>
{
gig.map(gigs => {
return gigs.user
})
}
</div>
)
}
export default UniqueVenueListing
Issue
You change the state shape. Initial shape of gig state is an empty array([]), but in the effect you store an object with an array under key gig ({ gig: filteredGigs }). Additionally since state updates are asynchronous, the console.log after setGig will only log the current state, not the one just enqueued.
Solution
Just save the filtered gig array into state. This will keep the gig state an array and later in the return gig.map(... will work as expected.
useEffect(()=>{
axios.get("https://us-central1-gig-fort.cloudfunctions.net/api/getGigListings")
.then(res => {
const filteredGigs = res.data.filter(gig => {
return gig.user !== authUserId
})
setGig(filteredGigs); // <-- store the array in state
})
},[])

How do I correctly implement setState() within useEffect()?

If this useEffect() runs once (using [] as the second parameter), setTicket(response.data) does not update the value of ticket with data. If I run useEffect() with [ticket] as the parameter, it updates the value of ticket with data, but useEffect becomes an infinite loop.
I need it to run once and update the ticket data. I don't think I understand useEffect() and its second parameter.
What do I do to get the expected result?
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
const EditTicket = (props) => {
const [ticket, setTicket] = useState("");
useEffect(() => {
axios
.get("http://localhost:4000/tickets/" + props.match.params.id)
.then((response) => {
setTicket(response.data);
console.log({ ticket });
})
.catch(function (error) {
console.log(error);
});
}, []);
return <div>edit</div>;
};
export default EditTicket;
ticket is a local const. It will never change, and that's not what setTicket is trying to do. The purpose of setTicket is to tell the component to rerender. On that next render, a new local variable will be created, with the new value.
Your code is already written the way it should be written, except that your log statement is not providing you with any useful information. If you want to see that it rerenders with the new value you could move the log statement to the body of the component.
const EditTicket = (props) => {
const [ticket, setTicket] = useState("");
console.log('rendering', ticket);
useEffect(() => {
// same as before

Resources