React axios await dispatch in UseEffect - javascript

I need to wait for my axios action to dispatch before continuing in UseEffect
import { getBrief } from '../../store/actions/agency-brief'
import agencyBriefReducer from '../../store/reducers/agency-brief-reducer'
const [agencyBrief, dispatch] = useReducer(agencyBriefReducer, [])
useEffect(async () => {
await getBrief(briefId)(dispatch);
console.log('BRIEF', agencyBrief)
}, [])
So console log here is undefined, I need to wait before
action:
export const getBrief = (briefID) => async (dispatch) => {
try {
let res = await axiosInstance.get(`/agency_briefs/reactGetBrief/${briefID}`)
console.log('sre', res )
return dispatch({
type: 'GET_BRIEF',
payload: res.data
})
}
catch (err) {
console.error(err);
}
}
reducer:
const agencyBriefReducer = (state, {payload, type}) => {
switch (type) {
case 'GET_BRIEF':
return {
...state,
brief: payload
}
default:
return state
}
}
I've tried async / await but it doesn't work, I need to wait so I can use the result to set the state ? Thanks

ok, so one of the comments suggested 2 useEffects and that does the trick:
useEffect(() => {
getBrief(briefId)(dispatch);
}, [])
useEffect(() => {
console.log('BRIEF', agencyBrief)
}, [agencyBrief.brief])

const [agencyBrief, dispatch] = useReducer(agencyBriefReducer, [])
useEffect(async () => {
await getBrief(briefId)(dispatch);
console.log('BRIEF', agencyBrief) // will always console []
}, [])
Start by explaining why the source code does not behave as expected. useEffect(() => {}, []) Since the dependency is an empty array, this method will only be executed once when the page is initialized. In this render, the value of agencyBrief is always [], will not change.
If you want to monitor the change of the result after the await asynchronous request ends, you can do this:
const [agencyBrief, dispatch] = useReducer(agencyBriefReducer, [])
useEffect(async () => {
await getBrief(briefId)(dispatch);
}, [])
useEffect(() => {
// Now agencyBrief is the latest value after await async request ends
console.log('BRIEF', agencyBrief)
}, [agencyBrief])

You can achieve it, I hope that would help.
import { getBrief } from '../../store/actions/agency-brief'
import agencyBriefReducer from '../../store/reducers/agency-brief-reducer'
const [agencyBrief, dispatch] = useReducer(agencyBriefReducer, []);
const getBrief = async () => {
await getBrief(briefId)(dispatch);
}
useEffect(() => {
getBrief();
}, [])
useEffect(() => {
if (agencyBrief) {
// perform action
}
}, [agencyBrief]);

This should resolve your issue .
import { getBrief } from '../../store/actions/agency-brief'
import agencyBriefReducer from '../../store/reducers/agency-brief-reducer'
const [agencyBrief, dispatch] = useReducer(agencyBriefReducer, [])
useEffect(async () => {
await getBrief(briefId)(dispatch);
setTimeOut(()=>console.log('BRIEF', agencyBrief),10);
}, [])

Related

change variable value with axios, useeffect, and usestate

i'm newbie here, i'm stuck. i want to change value from false to true, to stop shimmering when data sucessfully to load.
i have action like this
import axios from "axios";
import { CONSTANT_LINK } from "./constants";
import { GET } from "./constants";
import { ERROR } from "./constants";
import { connect } from 'react-redux';
export const addData = () => {
return (dispatch) => {
axios
.get(CONSTANT_LINK)
.then((res) => {
dispatch(addDataSuccess(res.data));
})
.catch((err) => {
dispatch(errorData(true));
console.log("error");
});
};
};
const addDataSuccess = (todo) => ({
type: GET,
payload: todo,
});
const errorData = (error) => ({
type: ERROR,
payload: error,
});
and this is my homepage which influential in this matter
const [shimmerValue, setShimmerValue] = useState(false)
useEffect(() => {
setShimmerValue(true)
dispatch(addData());
}, []);
<ShimmerPlaceholder visible={shimmerValue} height={20}>
<Text style={styles.welcomeName}>Welcome,Barret</Text>
</ShimmerPlaceholder>
i dont understand how it works
You can pass callback like this
const [shimmerValue, setShimmerValue] = useState(false);
const updateShimmerValue = () => {
setShimmerValue(true);
}
useEffect(() => {
// setShimmerValue(true) // remove this from here
dispatch(addData(updateShimmerValue)); // pass callback as param here
}, []);
Callback call here like
export const addData = (callback) => {
return (dispatch) => {
axios
.get(CONSTANT_LINK)
.then((res) => {
....
callback(); // trigger callback like this here
})
.catch((err) => {
....
});
};
};
you can use it:
const [shimmerValue, setShimmerValue] = useState(false)
useEffect(() => {
setState(state => ({ ...state, shimmerValue: true }));
dispatch(addData());
}, [shimmerValue]);

Multiple useEffect not working as expected

useEffect(() => {
debugger;
}, [filter]);
// eslint-disable-next-line
useEffect(async () => {
if (parseInt(localStorage.getItem("lastFetchTime")) + 8640000 > Date.now()) {
setRecipeList(JSON.parse(localStorage.getItem("recipeList")));
setIsLoading(false);
} else {
await fetch('https://api.spoonacular.com/recipes/random?number=20&apiKey=3c6b5aedfaf34bb899d1751ea2feb1b2')
.then((resp) => resp.json())
.then((data) => {
setRecipeList(data.recipes);
setIsLoading(false);
localStorage.setItem("recipeList", JSON.stringify(data.recipes));
localStorage.setItem("lastFetchTime", Date.now());
})
}
}, []);
I have these 2 useEffect in my program, the first one, with the listener is not being called even if the filter is changed. But it works if I remove the [] from the 2nd useEffect and the 2nd one runs on loop so I cant use it like that. I saw multiple forums, all of which suggests this should work.
import { useState, useEffect } from "react";
import { render } from "react-dom";
const sleep = (ms: number) => new Promise(
resolve => setTimeout(() => resolve('Resolved'), ms));
function App() {
const [filter, setFilter] = useState({ count: 0 });
const [get, set] = useState(0);
useEffect(() => {
console.log('Here');
}, [filter]);
useEffect(() => {
async function myFunction() {
const res = await sleep(5000)
.then(res => console.log(res));
setFilter({ ...filter, count: filter.count + 1 });
}
myFunction();
}, [get]);
return (
<div>
<p>App {get}</p>
<button onClick={() => set((get: number) => get + 1)}>
Click
</button>
</div>
);
}
render(<App />, document.getElementById("root"));
This minor snippet to be working for me as expected.
useEffect cannot be async. If you want to call an async function in useEffect() you need to do it like this:
EDIT: this is the complete useEffect
useEffect(() => {
async function getData() {
if (
parseInt(localStorage.getItem("lastFetchTime")) + 8640000 >
Date.now()
) {
setRecipeList(JSON.parse(localStorage.getItem("recipeList")));
setIsLoading(false);
} else {
const res = await fetch(
"https://api.spoonacular.com/recipes/random?number=20&apiKey=3c6b5aedfaf34bb899d1751ea2feb1b2"
);
const data = await res.json();
setRecipeList(data.recipes);
setIsLoading(false);
localStorage.setItem("recipeList", JSON.stringify(data.recipes));
localStorage.setItem("lastFetchTime", Date.now());
}
}
getData();
}, []);
I tested it and it worked as expected (I console.log() in the other useEffect())
There's nothing wrong with the useEffect. It's a bullet proof. But you make sure the following things:
Is filter updated during the component did mount?
The debugger will show up if you have open developer tool.
Isfilter updated during the component did update?
The debugger won't show up.
To make sure whenfilter is updated, use another effect hook but this time without dependency array.
useEffect(()=>{
console.log(filter) // analyze in the console
})
And if the value is updated during the update then you don't need to use dependency array but check the changes inside the effect hook by using some state for that as filter is coming from the update (props).
import { useState, useEffect, useCallback } from "react";
function App() {
const [isLoading, setIsLoading] = useState(false);
const [filter, setRecipeList] = useState({});
useEffect(() => {
// debugger;
}, [filter]);
// eslint-disable-next-line
const fetchData = useCallback(async () => {
if (
parseInt(localStorage.getItem("lastFetchTime")) + 8640000 >
Date.now()
) {
setRecipeList(JSON.parse(localStorage.getItem("recipeList")));
setIsLoading(false);
} else {
const data = await fetch(
"https://api.spoonacular.com/recipes/random?number=20&apiKey=3c6b5aedfaf34bb899d1751ea2feb1b2"
).then((resp) => resp.json());
setRecipeList(data.recipes);
setIsLoading(false);
localStorage.setItem("recipeList", JSON.stringify(data.recipes));
localStorage.setItem("lastFetchTime", Date.now());
}
}, []);
useEffect(() => {
setIsLoading(true);
fetchData();
}, [fetchData]);
return (
<div>
<span>{isLoading ? "loading" : "loaded!"}</span>
{!isLoading && filter && <div>filter size:{filter.length}</div>}
</div>
);
}
export default App;
I think it will work properly.
Thanks.

How to update an array using useState Hook

I've tried to fetch data from a URL and get the result as JSON format, then store not of the object result in my state. but it always returns an empty array.
const [genres, setGenres] = useState([]);
useEffect(() => {
const getGenres = async () => {
fetch("https://quote-garden.herokuapp.com/api/v2/genres")
.then((response) => response.json())
.then((data) => {
for (const g of data.genres) {
setGenres((oldGenres) => [...oldGenres, g]);
}
});
};
getGenres();
}, []);
Here is the code:
I don't see where the problem can be.
ps: I deleted the import so the code is more readable
import React, { useEffect, useState } from "react";
function App() {
const [quoteOfTheDay, setQuoteOfTheDay] = useState("");
const [authorOfQod, setAuthorOfQod] = useState("");
useEffect(() => {
const getQuoteOfTheDay = async () => {
fetch("https://quotes.rest/qod?language=en")
.then((response) => response.json())
.then((data) => {
const qod = data.contents.quotes[0].quote;
const author = data.contents.quotes[0].author;
setQuoteOfTheDay(qod);
setAuthorOfQod(author);
});
};
getQuoteOfTheDay();
}, []);
const [genres, setGenres] = useState([]);
useEffect(() => {
const getGenres = async () => {
fetch("https://quote-garden.herokuapp.com/api/v2/genres")
.then((response) => response.json())
.then((data) => {
for (const g of data.genres) {
setGenres((oldGenres) => [...oldGenres, g]);
}
});
console.log(genres); // genres always empty
};
getGenres();
}, []);
return (
<div className="app">
<Head quoteOfTheDay={quoteOfTheDay} author={authorOfQod} />
<div className="app__category">
<QuoteCategory genre="sport" />
</div>
</div>
);
}
export default App;
Thank you so much
I think it should work if you change
for (const g of data.genres) {
setGenres((oldGenres) => [...oldGenres, g]);
}
to
setGenres((oldGenres) => [...oldGenres, ...data.genres]);
Are you sure that
useEffect(() => {
const getGenres = async () => {
fetch("https://quote-garden.herokuapp.com/api/v2/genres")
.then((response) => response.json())
.then((data) => {
setGenres(data.genres);
});
};
getGenres();
}, []);
is not enough? :)
Up. If you began you can use async-await syntax till the end. It looks more neatly.
useEffect(() => {
const getGenres = async () => {
const response = await fetch("https://quote-garden.herokuapp.com/api/v2/genres");
const { genres } = await response.json();
setGenres(genres);
};
getGenres();
}, []);
you should put genresState as your dependency
const [genresState, setGenres] = useState([])
useEffect(() => {
const getGenres = async () => {
const response = await fetch("https://quote-garden.herokuapp.com/api/v2/genres");
const { genres } = await response.json();
setGenres(genres);
};
getGenres();
}, [genresState]);

How to call an async function inside a UseEffect() in React?

I would like to call an async function and get the result for my UseEffect.
The fetch api examples i found on the internet are directly made in the useEffect function.
If my URL changes, i must patch all my fetchs.
When i tried, i got an error message.
This is my code.
async function getData(userId) {
const data = await axios.get(`http://url/api/data/${userId}`)
.then(promise => {
return promise.data;
})
.catch(e => {
console.error(e);
})
return data;
}
function blabla() {
const [data, setData] = useState(null);
useEffect(async () => {
setData(getData(1))
}, []);
return (
<div>
this is the {data["name"]}
</div>
);
}
index.js:1375 Warning: An effect function must not return anything besides a function, which is used for clean-up.
It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately:
useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or state
Create an async function inside your effect that wait the getData(1) result then call setData():
useEffect(() => {
const fetchData = async () => {
const data = await getData(1);
setData(data);
}
fetchData();
}, []);
If you're invoking it right-away you might want to use it as an anonymous function:
useEffect(() => {
(async () => {
const data = await getData(1);
setData(data);
})();
}, []);
It would be best if you did what the warning suggests - call the async function inside the effect.
function blabla() {
const [data, setData] = useState(null);
useEffect(() => {
axios.get(`http://url/api/data/1`)
.then(result => {
setData(result.data);
})
.catch(console.error)
}, []);
return (
<div>
this is the {data["name"]}
</div>
);
}
If you want to keep the api function outside of the component, you can also do this:
async function getData(userId) {
const data = await axios.get(`http://url/api/data/${userId}`)
.then(promise => {
return promise.data;
})
.catch(e => {
console.error(e);
})
return data;
}
function blabla() {
const [data, setData] = useState(null);
useEffect(() => {
(async () => {
const newData = await getData(1);
setData(newData);
})();
}, []);
return (
<div>
this is the {data["name"]}
</div>
);
}
Since getData returns a Promise you could just use .then
useEffect(() => {
getData(1).then(setData);
}, []);
Component might unmount or re-render with different someId before await is resolved:
const unmountedRef = useRef(false);
useEffect(()=>()=>(unmountedRef.current = true), []);
useEffect(() => {
const effectStale = false; // Don't forget ; on the line before self-invoking functions
(async function() {
// You can await here
const response = await MyAPI.getData(someId);
/* Component has been unmounted. Stop to avoid
"Warning: Can't perform a React state update on an unmounted component." */
if(unmountedRef.current) return;
/* Component has re-rendered with different someId value
Stop to avoid updating state with stale response */
if(effectStale) return;
// ... update component state
})();
return ()=>(effectStale = true);
}, [someId]);
Consider using Suspense for data that needs to be loaded before component is mounted.
You can still define the async function outside of the hook and call it within the hook.
const fetchData = async () => {
const data = await getData(1);
setData(data);
}
useEffect(() => {
fetchData();
}, []);

Data initialization in useEffect triggers multiple requests

This is a follow-up question to this one.
Simply, I'm fetching the same date in two different ways. One by one and all together for updates. I have a simple store with context and useReducer.
My current code is like that:
import React, { useEffect } from "react";
import axios from "axios";
import { useGlobalState } from "./state";
const arr = Array.from(Array(100), (x, i) => i + 1);
function App() {
const [{ posts, init }, dispatch] = useGlobalState();
useEffect(() => {
const getInc = () => {
arr.forEach(async id => {
const res = await axios(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
dispatch({
type: "INC",
payload: res.data
});
});
};
const getAll = async () => {
const promises = arr.map(id =>
axios(`https://jsonplaceholder.typicode.com/posts/${id}`)
);
const res = await Promise.all(promises);
dispatch({
type: "ALL",
payload: res.map(el => el.data)
});
};
if (init) {
getInc();
} else {
getAll();
}
setInterval(() => getAll(), 10000);
}, [dispatch, init]);
return (
<>
<div>{posts.length}</div>
</>
);
}
export default App;
In every interval getAll is triggered twice.Here is a working sandbox.
I added a console.log to reducer part so you can see it runs twice. I can also see it in the network tab.
Try Separate Concerns when using useEffect, like you mentioned "One by one and all together for updates".
function App() {
const [{ init, posts }, dispatch] = useGlobalState();
useEffect(() => {
setInterval(() => getAll(dispatch), 10000);
}, [dispatch]);
useEffect(() => {
init ? getInc(dispatch) : getAll(dispatch);
}, [init, dispatch]);
...
}
Note:
useEffect(() => {
init ? getInc(dispatch) : getAll(dispatch);
}, [init, dispatch]);
After init turns to true, getAll gets called twice, once from upcoming interval and one from the useEffect above.
So in summary, on your Network, first 3 post request intervals with ids [0-99] are:
from getInc
from getAll in useEffect with init
from getAll in interval

Categories

Resources