Set state function is not properly updating the state - reactjs

const component = () => {
const [data, setData] = useState(null);
const fetchData = async () => {
try {
new DataService().getData().then((response) => {
setData(response);
console.log(data);
console.log(response);
}
} catch (error) {
console.log(error);
}
}
useEffect(() => {
fetchData();
}, []);
}
Why does console.log(data) display null even though console.log(response) displays the correct data? The data state should've been set before I console.log'd it, no?

The data state should've been set before I console.log'd it, no?
No.

This is most likely because setData is asynchronous. Since you're immediately asking for the value of data after using setData it's likely that the value hasn't had the chance to update yet.
If you're a Chrome user, have you used React Developer Tools? This will allow you to check the state of a component without needing to rely on console.log.

Related

Asynchronous function makes request without data when page reloads

So the problem here is I have this asynchronous function that makes request with string variable, but when page reloads it makes same request without this string despite the fact that variable itself is not empty. As a result I am receiving an error 'Bad Request' because text was not provided. Would someone be so kindly to explain me how thing works here so i could fix it so that those requests after page reloading were sent with data ?!
const { text, setText } = useContext(TextContext);
const [isLoading, setLoading] = useState(true);
const [entitiesData, setEntitiesData] = useState([]);
const call_razor = async (text_encoded) => {
try {
console.log(text_encoded) //here it shows data even when described error occurs after
const response = await axios.post('https://api.textrazor.com/',
"extractors=entities&text="+text_encoded,
{
headers: {
'x-textrazor-key': API_KEY,
'Content-Type': 'application/x-www-form-urlencoded',
}
});
setEntitiesData(response.data.response.entities)
setLoading(false)
} catch (err) {
console.log(err)
console.log(err.response.data.error)
console.log(err.response)
setLoading(false)
}
}
const dataFetch = async () => {
let textEncoded = encodeURIComponent(text)
await call_razor(textEncoded).then(() => splitIntoSentences())
}
useEffect(() => {
if (text) {
localStorage.setItem('text', text)
} else {
setText(localStorage.getItem('text'))
}
dataFetch();
}, [isLoading]);
The problem you're encountering is likely due to the fact that the useEffect hook is running before the text value is set from the TextContext context object.
One way to fix this issue is to move the useEffect hook to the parent component that is providing the TextContext context object, and pass the text value as a prop to the child component that is making the API request. This way, the text value will be available to the child component before the useEffect hook is run.
Another way would be to add a check for the text variable being empty or not in dataFetch() function, If it's empty, you can set isLoading to false so that it doesn't trigger the useEffect callback function.
const dataFetch = async () => {
if(text){
let textEncoded = encodeURIComponent(text)
await call_razor(textEncoded).then(() => splitIntoSentences())
}else {
setLoading(false)
}
}
You can also move the dataFetch() function call inside the useEffect callback after the text value is set.
useEffect(() => {
if (text) {
localStorage.setItem('text', text)
dataFetch();
} else {
setText(localStorage.getItem('text'))
}
}, [isLoading]);

How to stop useEffect from making so many requests? Empty Dependencies don't work

I have a component that updates a piece of state but I'm having issues with it
I have the state declared
const [data, setData] = useState([]);
Then in my useEffect I am
useEffect(() => {
const fetchData = async () => {
await axios
.get(
API_URL,
{
headers: {
'Content-Type': 'application/json',
'X-API-KEY': API_KEY
},
params:{
"titleId": id
}
}
)
.then((response) => {
setData(response.data.Item);
})
.catch((err) => {
console.error("API call error:", err.message);
});
}
fetchData();
}, [data, id])
If I declare "data" in my dependencies, I get an endless loop of requests which is obviously no good. But if I leave 'data' out from the dependencies it shows nothing, though I am successfully retrieving it in my network's tab and even when I {JSON.styringify(data)} in a div tag aI get the json content too. So the info is in the DOM, but it's not updating the components
How can I do this so I can make an initial request to load the data and not thousands of them?
I've tried the following:
a setTimeout on the callback function
the isCancelled way with a return (() => { callbackFunction.cancel(); })
And there is an Abort way of doing this too but I can't figure it out. Every example I've seen is for class components
Sorry for the vague code. I can't replicate this without lots of coding and an API. Thanks in advance
You want to set the state and then check if is different. I use a custom hook for this which uses the useRef hook:
export function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
const prevData = usePrevious(data);
I don't know what your data looks like, but build a conditional from it. Inside of your useEffect you'll need something like:
if (data !== prevData) fetchData()
or
if (data.id !== prevData.id) fetchData()
You'll then add prevData to you dependencies:
[data, prevData, id]
So useEffects works with dependency.
With dependency - on changing dependency value useEffect will trigger
useEffect(() => {
// code
}, [dependency])
With empty brackets - will trigger on initial of component
useEffect(() => {
// code
}, [])
Without dependency and Brackets - will trigger on every state change
useEffect(() => {
// code
})
Do something like this, if that can help. I also used async/await so you can check that.
const App = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(API_URL, {
headers: {
'Content-Type': 'application/json',
'X-API-KEY': API_KEY,
},
params: {
titleId: id,
},
});
setData(response.data.Item);
} catch (err) {
console.error('API call error:', err.message);
}
};
fetchData();
}, [id]);
if (!data.length) return null;
return <p>Yes, I have data</p>;
};
obviously you will get an infinit loop !
you are updating the data inside your useEffect which means each time the data changes, triggers useEffect again and so on !
what you should do is change your dependencies depending on your case for example :
const [data, setData] = useState([])
const [fetchAgain, setFetchAgain] = useState(false)
useEffect(()=> {
fetchData();
}, [])
useEffect(() => {
if(fetchAgain) {
setFetchAgain(false)
fetchData();
}
}, [fetchAgain])
now each time you want to fetch data again you need to update the fetchAgain to true

setState never gets set in useEffect after API call responds with data

I'm trying to update state immediately after data comes in from the API. The data is coming in, I can see it using the console.log right below my API request. All of the data is right but for some reason, setState never sets in my hook. It just returns and empty array even after the console displays data.
const [experienceData, setExperienceData] = useState([]);
const { match = {} } = props;
useEffect(() => {
async function fetchData() {
if (Object.keys(match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData(response)
}
}
fetchData();
}, []);
I must be doing something wrong but I can't figure out what that is. Hoping someone on here has run into the same issue.
UPDATE: I just changed everything over the a class and duplicated the exact code on another file and ran into the exact same issue. The console updates with the data, but the setState on the line immediately after the data does not setState.
async componentDidMount() {
if (Object.keys(this.props.match.params).length > 0) {
const response = await ApiService.getExperiences(this.props.match.params.experieneId);
console.log(response[0])
this.setState({ experienceData: response[0], occurrenceData: response[0].occurrences });
}
}
You have to useSetState in a proper way, the issue is in the setExperienceData
const [experienceData, setExperienceData] = useState({response:""});
const { match = {} } = props;
useEffect(() => {
async function fetchData() {
if (Object.keys(props.match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData(experienceData => ({ ...experienceData, response: response }));
}
}
fetchData();
}, []);
return(<div>check {experienceData.response}</div>)
I see you left the dependency array empty. This tells React to run this effect only once: when the component first renders. If you want your useEffect to respect your state hook, put setExperienceData inside the dependency array
const [experienceData, setExperienceData] = useState([]);
const { match = {} } = props;
useEffect(() => {
fetchData();
}, [props.match.params]);
const async fetchData = () => {
if (Object.keys(match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData([...response])
}
}
Could you please try passing [match.params] as the second argument to your useEffect.

How to setstate after fetch data React hook [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 10 months ago.
Code :
Result : Not have data in state
help me pls , thanks!
setState is asynchronous that's why you are seeing books as empty array. Here is a quote from the React docs:
The setState function is used to update the state. It accepts a new
state value and enqueues a re-render of the component.
One thing you may be doing wrong is in your useEffect callback. If your effect returns a function, React will run it when it is time to clean up. And you don't want the setState functions in fetchData to be invoked during clean up as the component will probably be unmounted.
If you just want the fetchData to only run once after the component mounts, here is a possible solution:
useEffect(() => {
// put the fetchData inside the effect
async function fetchData() {
setLoading(true);
const name = await getNameGroup();
const tmp = await getAll(name);
console.log(tmp);
setBooks(tmp);
console.log(books); // may not be the same as tmp, but you will see the updated state in the next render
setLoading(false);
}
fetchData();
},[]}
You should read more about useEffect hook in the React docs.
It's a stale closure problem.
Your useEffect where the fetchData is being called, has an empty dependency array. Within the fetchData function, which is inside useEffect, you are trying to print books which one first load, was initialized with an empty array.
All hooks hold the same reference to the variables with which they were initialized, till the dependencies change. To get an updated state, they depend on the dependency array. Since your dependency array doesn't specify books, it won't refresh the reference of books in your fetchData function either. Read more about the stale closure problem here
That's why your books variable is showing stale data.
export default function() {
// fetch data here
// runs only once because of empty dependency array
useEffect(() => {
let isCancelled = false
// define the fetchData inside the `useEffect` so that
// you can detect if the component has been unmounted
// using `isCancelled`
const fetchData = async () => {
const tmp = await getAll()
// only update state if component isn't unmounted
// if you try to update state on an unmounted component,
// React will throw an error
if (!isCancelled) {
setIsLoading(false)
setBooks(tmp)
}
}
if (!isCancelled) {
setIsLoading(true)
fetchData()
}
// cleanup
return () => {
isCancelled = true
}
}, [])
}
const [dataArray, setDataArray] = useState([]);
async function fetchData() {
try {
setIsLoading(true);
const response = await getNameGroup();
setDataArray(response);
} catch(error) {
// handle error
} finally {
setIsLoading(false);
}
}
This is an example code that is working and you can apply:
const [data, setData] = useState([]);
const [hasError, setErrors] = useState(false);
async function fetchData() {
const LibraryQuery = JSON.stringify({query: `query { species { id name description } }`});
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const res = await fetch('http://localhost:3000/graphql',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': token
},
body: LibraryQuery
});
res
.json()
.then(res => setData(res.data))
.catch(err => setErrors(err));
}
useEffect(() => {
fetchData();
}, []);

React Native, problem while updating State inside useEffect from async function

I'm trying the fetch data from a json API and setting it to a state. Currently using visual studio code with a pixel 4 emulator.
When I try to update my state inside of a useEffect method on the emulator's first launch or on reload, it doesn't change. If I save in vs code, the data in state updates as intended.
...
import React, {useState, useEffect} from 'react';
import {getJsonData} from './getJsonData';
const myApp = () => {
const [state, setState] = useState({
isLoading: true,
data: null,
});
const updateState = data => {
console.log(data); //Logs the correct Json data everytime
setState(state => ({...state, isLoading: false, data: data}));
console.log(state.isLoading); //Doesn't update on reload (updates only on save)
console.log(state.data); //Same as above
};
useEffect(() => {
getJsonData().then(data => updateState(data));
}, []);
return (
<View>
<Text>{state.data.title}</Text>
<Text>{data.data.completed}</Text>
</View>
);
}
And this is the getJsonData( ) function:
export async function getJsonData() {
try {
let response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
let responseJson = await response.json();
return responseJson;
} catch (error) {
console.error(error);
}
}
I ultimately want the state to update on the application's first run, on reload, as well as each time I call a certain reloadApp( ) function.
If the above code is not the best practice, please don't hold back to correct me as I'm just learning about states.
setState function is asynchronous. So console.log immediately after setState will give old value but not the new value.
Also why don't you seperate the states like
const [isLoading,setIsLoading]=useState(true);
const [data,setData] = useState(null);
and set them separately so your code looks better.
in updateState(jsonData) you can do then
setIsloading(false);
setData(jsonData);

Resources