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

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.

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]);

Read the setState value immediately after setting in useEffect React

I want to fetch info from the API in useEffect, set it using setState and immediately use it for further filtering.
the code looks like this:
const[usersInfo, setUsersInfo] = setState('')
useEffect(()=>{
async function fetchUsers(){
const response = await fetch(`http://localhost:8083/api/patient/findAll`);
const json = await response.json();
setUsersInfo(json)
}
fetchUsers()
console.log('users info', usersInfo)
},[])
I tried to pass the dependency for usersInfo but then it is running in loop.
What can I do it to prevent this behavior?
You need to add a second useEffect that detects changes to usersInfo.
const [usersInfo, setUsersInfo] = setState("");
useEffect(() => {
async function fetchUsers() {
const response = await fetch(`http://localhost:8083/api/patient/findAll`);
const json = await response.json();
setUsersInfo(json);
}
fetchUsers();
// console.log("users info", usersInfo); // commenting out this code since it will always print empty string
}, []);
useEffect(() => {
if(usersInfo !== ""){
// Do stuff here with usersInfo
}
}, [usersInfo]);

useEffect not triggering but the template is being rendered somehow

I am getting too many re-renders while using react-hooks.
I am trying to fetch data from api by using a parameter in URL.
Here's the code:
export default function Details() {
const { title } = useParams();
const [loading, setLoading] = useState(true);
const [details, setDetails] = useState([]);
const [error, setError] = useState("");
function getDetails(keyword) {
if (keyword) {
setLoading(true);
fetch(
`API`
)
.then((res) => {
let result = res.data.results;
result = result.filter(function (result) {
return (result.title = keyword);
});
setDetails(result[0]);
setLoading(false);
console.log(details);
})
.catch((err) => {
setError(["Unable to fetch data"]);
setLoading(false);
});
}
}
getDetails(title)
return(
// template
)
now I think this is happening at the line where I call getDetails.
then I tried using useEffect to load the data only once after it is mounted,
useEffect(() => {
getDetails(title);
}, []);
It still is unable to fetch the data, as the getDetails function is never called.
How can I resolve this?
Edit:
Fixed one silly error.
Here's the codesandbox link:
Codesandbox
There are multiple issues with this, first you need to specify what you want to be notified about when the useEffect gets called again. You could do this by adding the variables you want within the array
useEffect(() => {
getDetails(title);
}, [
// Add what you want here
]);
The second issue you have is that you declared the detalis variable twice. Once using the set state here: const [details, setDetails] = useState([]);
The second time here:
const details = getDetails(title)
the code here has two obvious error beside the functionality problems you mentioned:
1 - you cannot declare two variables with same name using let or const; it will throw a SyntaxError
const [details, setDetails] = useState([]);
...
const details = getDetails(title)
2- getDetails function is written with a asynchronous mindset, and it will return nothing,
so details in const details = getDetails(title) will be set to undefined
Looks like your getDetails function has title param so I would add title and getDetails both in the dependency list of useEffects
Or place getDetails inside the useEffect
Here is your working code. You had multiple problems where res.data was undefined so you need to get res.results directly based on your response object
useEffect(() => {
function getDetails(keyword) {
if (keyword) {
setLoading(true);
fetch(
`https://api.jikan.moe/v3/search/anime?q=${keyword}&page=1&genre_exclude=0`
)
.then((res) => res.json())
.then((res) => {
console.log(res.results);
let result = res.results;
console.log(result);
result = result.filter(function (result) {
return (result.title = keyword);
});
setDetails(result[0]);
setLoading(false);
console.log(3);
})
.catch((err) => {
console.log(err);
setError(["Unable to fetch data"]);
setLoading(false);
});
}
}
console.log('calling getDetails')
getDetails(title);
}, [title]);
Note: tested in the code sandbox link provided in the question. Its working code.

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();
}, []);

Custom react hook causes infinite loop

I have a custom fetch hook:
export const useFetch = (url: string) => {
const [response, setResponse] = useState<any>(null);
const [error, setError] = useState<any>(null);
const fetchList = (url: string) => {
return API.get(AMPLIFY_ENPOINTS.default, url, { response: true });
};
useEffect(() => {
const fetchFunc = async () => {
try {
const fetchResponse = await fetchList(url);
setResponse(fetchResponse.data);
} catch (error) {
setError(error);
}
};
fetchFunc();
}, [url]);
return { response, error };
};
This I then use in a component:
const fetchOrders = useFetch(apiUrl);
useEffect(() => {
const { response, error } = fetchOrders;
if (error) setError(error);
if (response) {...}
}, [fetchOrders]);
And this causes an infinite loop, how should I go about fixing it?
The fetchOrder reference keeps changes on each re-render since you are returning a newly created object each time and hence it triggers an infinite loop when you call setError within your useEffect.
Instead of adding fetchOrders as a dependency, add response and error separately
const { response, error } = useFetch(apiUrl);
useEffect(() => {
if (error) setError(error);
if (response) {...}
}, [response, error]);
It is this line that is responsible for this
return { response, error };
And of course his partner in crime is this [fetchOrders] dependency array in the second useEffect.
The first line returns a new object and thus fetchOrders is always a new value.
After thinking about your code, I came up to conclude that you probably don't need the second useEffect at all (I think).
// the useFetch only re-fetches when url changes
const { response, error } = useFetch(url);
// use response directly, you don't need to re-set the error nor the response in your component.
But if you were to do a side effect with response, you can consider upgrading your useFetch hook to accept a callback parameter and useEffect with a response dependency.
In your component, try wrapping the input in useMemo.
const url = useMemo(() => apiUrl, [])
const fetchOrders = useFetch(url);
useEffect(() => {
const { response, error } = fetchOrders;
if (error) setError(error);
if (response) {...}
}, [fetchOrders]);
Otherwise, apiUrl is a local variable and gets recreated every time the functional component runs, causing the infinite loop.

Resources