why is my state not updated in useEffect? - reactjs

const user = useSelector(state => state.user)
const [gioHangChiTiet, setGioHangChiTiet] = useState([])
const [gioHangSanPham, setGioHangSanPham] = useState([])
useEffect(() => {
const dataGioHang = async () => {
try {
const res = await axios.get(`${apiUrl}api/giohangs`, {
headers: {
token: `Bearer ${user.user?.accessToken}`
}
})
console.log(res.data.data.sach)
setGioHangChiTiet(res.data.data)
console.log(gioHangChiTiet "it is empty")
} catch (error) {
console.log(error)
}
}
if (user.user) {
dataGioHang()
// console.log(gioHangChiTiet)
}
}, [user])
That is my code. I trying to save gioHangChiTiet with new data but it's always is an empty array. I try console.log this and I think it will work but it's not. But if I change any thing in this code, gioHangChiTiet will update new data and console.log this. Can anyone help me and explain why? Thank you so much. I spent a lot of time figuring out how to solve it :(( . UPDATED : I fixed it. Thanks a lots ( console.log not run because it in useEffect , if i console after useEffect, i will have true value)

const user = useSelector(state => state.user)
const [gioHangChiTiet, setGioHangChiTiet] = useState([])
const [gioHangSanPham, setGioHangSanPham] = useState([])
useEffect(() => {
const dataGioHang = async () => {
try {
const res = await axios.get(`${apiUrl}api/giohangs`, {
headers: {
token: `Bearer ${user.user?.accessToken}`
}
})
console.log(res.data.data.sach)
// setGioHangChiTiet(res.data.data.sach)
setGioHangChiTiet(res.data.data)
console.log(gioHangChiTiet)
} catch (error) {
console.log(error)
}
}
if (user.user) {
dataGioHang()
// console.log(gioHangChiTiet)
}
}, [user])
Add user to your dependency array. Otherwise the useEffect wont be able to check your if statement. If you're using CRA you should get a warning in your terminal.

useEffect takes two arguments first one is callback function and second one is dependency array.
useEffect(() => {
// this is callback function
},[ /* this is dependency array */ ])
If you want to trigger the callback function every time a state changes you need to pass that state in dependency array.
useEffect(() => {
console.log(someState)
},[someState])
In above code someState will get logged each time it's value changes.
If your dependency array is empty you useEffect callback function will trigger ONLY ONCE.
In your case if you want trigger callback function on change of user state or any other state simply pass it in dependency array.

Can you give this a try:
const user = useSelector(state => state.user)
const [gioHangChiTiet, setGioHangChiTiet] = useState([])
const [gioHangSanPham, setGioHangSanPham] = useState([])
useEffect(() => {
const dataGioHang = new Promise((resolve,reject) => {
( async() => {
try {
const res = await axios.get(`${apiUrl}api/giohangs`, {
headers: {
token: `Bearer ${user.user?.accessToken}`
}
})
console.log(res.data.data.sach)
setGioHangChiTiet(res.data.data.sach)
setGioHangChiTiet(res.data.data)
console.log(gioHangChiTiet)
resolve();
} catch (error) {
console.log(error)
reject()
}})();
})
if (user.user) {
dataGioHang().then(()={ console.log(gioHangChiTiet);
})
.catch(() => console.log("Error executing dataGioHang"))
}
}, [user])

Related

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

useEffect doesn't run after rendering

I'm kind of confused about how useEffect is triggered and how it work. I wrote a function like this but the useEffect doesn't run at all. I want to fetch the data from the API and then render a page based on the data. But it doesn't trigger the useEffect. If I don't use the useEffect, it will render the page three times.
async function getData() {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
tmpArrData = data;
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
function App() {
const [arrData, setArrData] = useState();
const [loadData, setLoadData] = useState(false);
useEffect(() => {
console.log("if it works, this line should be shown");
const tmpArrData = getData();
setArrData(tmpArrData);
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
The useEffect hook is guaranteed to run at least once at the end of the initial render.
getData is an async function and the useEffect callback code is not waiting for it to resolve. Easy solution is to chain from the implicitly returned Promise from getData and access the resolved value to update the arrData state. Make sure to remove the state from the useEffect's dependency array so that you don't create a render loop.
The getData implementation could be clean/tightened up by just returning the fetch result, no need to save into a temp variable first.
async function getData() {
return await fetch(".....")
.then((res) => res.json());
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData().then((data) => {
setArrData(data);
});
}, []); // <-- empty dependency so effect called once on mount
Additionally, since arrData is initially undefined, arrData[0] is likely to throw an error. You may want to provide valid initial state, and a fallback value in case the first element is undefined, so you don't attempt to access properties of an undefined object.
const [arrData, setArrData] = useState([]);
...
const data = arrData[0] || {}; // data is at least an object
return (
<GifCompoment
id={data.id}
name={data.name}
activeTimeTo={data.activeTimeTo}
activeTimeFrom={data.activeTimeFrom}
requiredPoints={data.requiredPoints}
imageUrl={data.imageUrl}
/>
);
You should call state setter insede of Promise
function App() {
const [arrData, setArrData] = useState();
function getData() {
fetch("/api/hidden")
.then((res) => res.json())
.then((data) => setArrData(data));
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData();
}, []);
return ...
}
By combining the answer from Drew Reese and Artyom Vancyan, I have solved my problem. I think the key points are setState right in the then function .then((data) => setArrData(data)) ,don't put the dependency in the useEffect, and await inside the useEffect. Thank you guy super ultra very much. Big love
useEffect(() => {
console.log("if it works, this line should be shown");
const getData = async () => {
await fetch("hidden API")
.then((ref) => ref.json())
.then((data) => {
setArrData(data);
});
}
getData();
}, []);
function App() {
const [arrData, setArrData] = useState([]);
const [loadData, setLoadData] = useState(false);
const async getData=()=> {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
setArrData(tmpArrData);
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
useEffect(() => {
console.log("if it works, this line should be shown");
const callApi =async()=>{
await getData();
}
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
Page will be rendered three to four times it's normal.

How do I properly set up my useEffect so I don't receive a missing dependency warning?

I am receiving this warning "React Hook React.useEffect has missing dependencies: 'fetchData' and 'source'. Either include them or remove the dependency array react-hooks/exhaustive-deps". This is my function:
function EmployeesPage(props: any) {
const companyId = props.match.params.id;
const source = axios.CancelToken.source();
const fetchData = async () => {
try {
const response = await axios.get<IEmployees[]>(`${process.env.PUBLIC_URL}/api/company/${companyId}/employees`, {
cancelToken: source.token
});
setEmployees(response.data);
setLoading(true);
} catch (error) {
if (axios.isCancel(error)) {
} else {
throw error;
}
}
}
const deleteEmployee = async (EmployeeId: any) => {
const response = await axios.delete(`${process.env.PUBLIC_URL}/api/company/${companyId}/employees/${employeeId}`);
if (response) await fetchData();
}
React.useEffect(() => {
fetchData()
return () => {
source.cancel();
};
}, [])
I tried to fix this by putting fetchData inside of the useEffect and moving the deleteEmployee out, but this causes my endpoint to be called in an infinite loop. Then I tried the useCallback function and also created an infinite loop.
const fetchData = useCallback(async () => {
try {
const response = await axios.get<IEmployees[]>(`${process.env.PUBLIC_URL}/api/company/${companyId}/employees`, {
cancelToken: source.token
});
setEmployees(response.data);
setLoading(true);
} catch (error) {
if (axios.isCancel(error)) {
} else {
throw error;
}
}
}, [source, CompanyId]);
React.useEffect(() => {
fetchData()
return () => {
source.cancel();
};
}, [fetchData, source])
const deleteEmployee = async (EmployeeId: any) => {
await axios.delete(`${process.env.PUBLIC_URL}/api/company/${companyId}/employees/${employeeId}`);
}
It is my understanding that the only thing that should be going in the dependency array would be something that is going to change. I think my dependency array should be empty because I don't want anything to change. It is going to be the same data being returned each time unless a new employee is added. I'm not sure how to fix this to get the warning message to go away. I have see that there is a way to disable the warning but I am not sure I should do that.
The effect runs in an infinite loop since the source object changes in every render. Move it inside the effect. And move the fetchData function inside the effect as well since it needs access to source.
You should add companyId to the dependencies array to make sure the data is refetched when companyId changes. The setEmployees and setLoading references don't change so it is safe to add them - they won't cause the effects to re-run.
React.useEffect(() => {
const source = axios.CancelToken.source()
const fetchData = async () => {
//...
}
fetchData()
return () => {
source.cancel()
}
}, [companyId, setEmployees, setLoading])
I would recommend reading this to understand if it is safe to omit functions from the dependencies array.
You could declare both fetchData and source inside the useEffect, since it does not use anything besides setState functions. This way, fetchData won't be declared over and over on each re-render.
useEffect(() => {
const source = axios.CancelToken.source();
const fetchData = async () => {
...
};
fetchData();
return () => {
source.cancel();
};
}, [setEmployee, setLoading]);

ts/react - fetch in useEffect gets called multiple times

in my functional component I want to fetch data once the component mounts. But unfortunately, the request gets fired three times, until it stops. Can you tell me why?
const [rows, setRows] = useState<any[]>([]);
const [tableReady, setTableReady] = useState<boolean>(false);
const [data, setData] = useState<any[]>([]);
const getData = async () => {
const user = await Amplify.Auth.currentAuthenticatedUser();
const token = user.signInUserSession.idToken.jwtToken;
const apiurl = 'xxx';
fetch(apiurl, {
method: 'GET',
headers: {
'Authorization': token
}
})
.then(res => res.json())
.then((result) => {
setData(result);
})
.catch(console.log)
}
useEffect(() => {
if (!tableReady) {
getData();
if (data.length > 0) {
data.forEach((element, i) => {
const convertedId: number = +element.id;
setRows(rows => [...rows, (createData(convertedId, element.user))]);
});
setTableReady(true);
}
}
}, []);
return (
<div className={classes.root}>
<MUIDataTable
title={""}
data={rows}
columns={columns}
/>
</div>
);
I updated my question due to the comment.
The useEffect is missing a dependency array, so its callback is invoked every time the component renders.
Solution
Add a dependency array.
useEffect(() => {
if (!tableReady) {
getData();
if (data.length > 0) {
data.forEach((element, i) => {
const convertedId: number = +element.id;
rows.push(convertedId);
});
setTableReady(true);
}
}
}, []); // <-- dependency array
An empty dependency array will run the effect once when the component mounts. If you want it to ran when any specific value(s) update then add these to the dependency array.
See Conditionally firing an effect
Edit
It doesn't appear there is any need to store a data state since it's used to populate the rows state. Since React state updates are asynchronously processed, and useEffect callbacks are 100% synchronous, when you call getData and don't wait for the data to populate, then the rest of the effect callback is using the initially empty data array.
I suggest returning the fetch request from getData and just process the response data directly into your rows state.
const getData = async () => {
const user = await Amplify.Auth.currentAuthenticatedUser();
const token = user.signInUserSession.idToken.jwtToken;
const apiurl = 'xxx';
return fetch(apiurl, {
method: 'GET',
headers: {
'Authorization': token
}
});
}
useEffect(() => {
if (!tableReady) {
getData()
.then(res => res.json())
.then(data => {
if (data.length) {
setRows(data.map(element => createData(+element.id, element.user)))
}
})
.catch(console.error)
.finally(() => setTableReady(true));
}
}, []);

React - How do I get fetched data outside of an async function?

I'm trying to get the data of "body" outside of the fetchUserData() function.
I just want to store it in an variable for later use.
Also tried modifying state, but didn't work either.
Thanks for your help :)
const [userData, setUserData] = useState();
async function fetchUserData () {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
});
const body = await result.json();
//setUserData(body);
return(
body
)
} catch (err) {
console.log(err);
}
}
let userTestData
fetchUserData().then(data => {userTestData = data});
console.log(userTestData);
//console.log(userData);
Use useEffect
async function fetchUserData () {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
})
return await result.json()
} catch (err) {
console.log(err)
return null
}
}
const FunctionalComponent = () => {
const [userData, setUserData] = useState()
useEffect(() => {
fetchUserData().then(data => {
data && setUserData(data)
})
}, []) // componentDidMount
return <div />
}
Ben Awad's awesome tutorial
Example:
it seems that you are making it more complicated than it should be. When you get the response i.e the resolved promise with the data inside the async function, just set the state and in the next render you should get the updated data.
Example:
const [userData, setUserData] = useState();
useEffect(() => {
const getResponse = async () => {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
});
const body = await result.json();
setUserData(body);
} catch (err) {
console.log(err)
}
}
getResponse();
}, [])
console.log(userData);
return <div></div>
Assuming the you need to call the function only once define and call it inside a useEffect or 'componentDidMount'. For using async function inside useEffect we need to define another function and then call it.
When you do
let userTestData
// This line does not wait and next line is executed immediately before userTestData is set
fetchUserData().then(data => {userTestData = data});
console.log(userTestData);
// Try changing to
async someAsyncScope() {
const userTestData = await fetchUserData();
console.log(userTestData)
}
Example:
state = {
someKey: 'someInitialValue'
};
async myAsyncMethod() {
const myAsyncValue = await anotherAsyncMethod();
this.setState({ someKey: myAsyncValue });
}
/*
* Then in the template or where ever, use a state variable which you update when
* the promise resolves. When a state value is used, once the state is updated,
* it triggers as a re-render
*/
render() {
return <div>{this.state.someKey}</div>;
}
In your example you'd use setUserData instead of this.setState and userData instead of {this.state.someKey}

Resources