Using useEffect properly when making reqs to a server - reactjs

I have a handleRating function which sets some state as so:
const handleRating = (value) => {
setCompanyClone({
...companyClone,
prevRating: [...companyClone.prevRating, { user, rating: value }]
});
setTimeout(() => {
handleClickOpen();
}, 600);
};
I think also have a function which patches a server with the new companyClone values as such:
const updateServer = async () => {
const res = await axios.put(
`http://localhost:3000/companies/${companyClone.id}`,
companyClone
);
console.log("RES", res.data);
};
my updateServer function gets called in a useEffect. But I only want the function to run after the state has been updated. I am seeing my res.data console.log when I load my page. Which i dont want to be making reqs to my server until the comapanyClone.prevRating array updates.
my useEffect :
useEffect(() => {
updateServer();
}, [companyClone.prevRating]);
how can I not run this function on pageload. but only when companyClone.prevRating updates?

For preventing function call on first render, you can use useRef hook, which persists data through rerender.
Note: useEffect does not provide the leverage to check the current updated data with the previous data like didComponentMount do, so used this way
Here is the code example.
https://codesandbox.io/s/strange-matan-k5i3c?file=/src/App.js

Related

How to get session data in next-auth after loading is done

I am trying to get data using useSession, and this data I store in my state, but when I get data using this, it returns me null object since data is still in loading state.
Is there any way I can get data only after status is not loading and till then block the page?
const { data: session, status } = useSession();
useEffect(() => {
const { data } = getCookieData(session);
if (data) setUser(() => data.user);
}, []);
Comment turned into an answer:
useSession changes the state after the status changes. If you want the code inside the useEffect to run after state changes, you probably want to put that state inside the brackets, so this code:
useEffect(() => {
const { data } = getCookieData(session);
if (data) setUser(() => data.user);
}, []);
Would become this
useEffect(() => {
const { data } = getCookieData(session);
if (data) setUser(() => data.user);
}, [data,status]);
And in general whenever you need to trigger some function every time a particular prop or state changes you should place those variables inside the useEffect()
More info about useEffect and lifecycles in the docs:
https://reactjs.org/docs/hooks-effect.html

How to properly re-render functional component after API call

When the page loads, I am making an API call, displaying a table with appointments. After the API call, I set a state for hasData to true, and the data is inserted in another setState. The issue is when the API returns the data from the async call, the component does not show the data. Please see code below.
const [recentAppointmentData, setRecentAppointmentData] = useState([])
const [hasAppointmentData, setHasAppointmentData] = useState(false)
const getAppointments = useCallback(() => {
const getAppointmentDataService = new GetAppointmentsService();
getAppointmentDataService.getDataFromService("263749804").then((results) => {
console.log("APPOINTMENT DATA ", results);
results.recentAppointments.map((result) => {
var recentAppointments = {
appointmentObject: {
serviceCategory: [],
serviceId: "",
appointmentDate: "",
groomer: "",
resourceId: "",
visitId: "",
},
};
if (result["services"] !== undefined) {
console.log("SERVICESS", result["services"]);
result["services"].map((service) => {
recentAppointments.appointmentObject.serviceCategory.push(
service["serviceCategory"]
);
recentAppointments.appointmentObject.serviceId = service["serviceId"];
});
}
recentAppointments.appointmentObject.appointmentDate = moment(
result["appointmentDateTime"]
).format("MM/DD/YY");
recentAppointments.appointmentObject.groomer = result["groomer"];
recentAppointments.appointmentObject.resourceId = result["resourceId"];
recentAppointments.appointmentObject.visitId = result["visitId"];
appointments.push(recentAppointments.appointmentObject);
Here I am setting the has Appointment data to true after the async function has been completed.
if (!hasAppointmentData) {
setHasAppointmentData(true);
}
});
Here I am storing the data in another state.
if (!hasAppointmentData) {
console.log("APPOINTMEN", appointments);
setRecentAppointmentData(appointments);
}
});
}, [hasAppointmentData]);
I am calling the function in the useEffect.
useEffect(() => {
getAppointments();
renderTabs();
}, [getAppointments, renderTabs]);
Can someone guide me on what I am doing wrong? Thanks
The problem is that you're using the useEffect hook wrong.
useEffect runs every time one of its dependencies change, or runs just once when the component mounts if you don't pass in any dependency to it. The dependencies are usually state variables within the component that useEffect runs in.
You want your getAppointments() to run only once, since it calls an external API to get the data. And you want to call renderTabs() (which I assume is responsible for displaying the data in the UI) only when the data is available. So you need to put them into two separate useEffect hooks.
useEffect(() => {
getAppointments();
}, []); // Runs just once when the component is mounted
useEffect(() => {
if (hasAppointmentData) {
renderTabs();
}
}, [hasAppointmentData]); // Runs every time the value of hasAppointmentData changes
But you'll need to watch out for a problem here, when using hasAppointmentData as the dependency. You're calling setHasAppointmentData first, and then following it up with setRecentAppointmentData. The second useEffect hook would run right after you set the boolean to true. By the time renderTabs() tries to fetch the data from recentAppointmentData, the data may not have been updated.
To me, hasAppointmentData is pretty much useless here. Checking for recentAppointmentData.length would serve you just as well, and is guaranteed to work reliably every time. So my second hook would look like this:
useEffect(() => {
if (recentAppointmentData.length) {
renderTabs();
}
}, [recentAppointmentData.length]);

Data is not set on time on the state using hooks in React JS

I am working on a Instagram clone project. I am doing the profile page part and got some problems.
I am trying to use hooks (useState) to set my response from the server with the user document. My problem is that when I use the useEffect (only once with []), the username state is not set.
I let useEffect run infinitely, and I found out that the state is set after some little time.
const getUser = async () => {
await axios.get(`/user/getbyusername/${props.match.params.username}`)
.then(res => {
console.log(res.data) // PRINTS DATA CORRECTLY
setProfileUser({
_id: res.data._id,
username: res.data.username,
fullName: res.data.fullName,
followersCount: res.data.followersCount,
followingCount: res.data.followingCount,
profileImage_PublicId: res.data.profileImage_PublicId,
bio: res.data.bio
})
console.log(profileUser)
})
}
// DOES NOT SET ANY DATA
useEffect(() => {
getUser(); // async function and awaits for the server response
// HERE, I call getUserPosts using profileUser._id, and it profileUser._id is not set yet
getPosts(); // async function as well
}, [])
Checking if setProfileUser(...) is correct, and it is because it sets the data, but after some time even though I could console.log(res.data) in the correct first time using [].
// WORKS, sets the user state after two runs. NOT GOOD PRACTICE
useEffect(() => {
getUser();
})
Add another useEffect hook that runs when profileUser changes, so that it runs after setProfileUser finishes:
useEffect(getUser, []);
useEffect(() => {
if (profileUser) {
getPosts();
}
},
[profileUser],
);

React useEffect infinite loop fetching data from an api

Hi I'm trying to make a twitter clone app. I am using React on the client side and Express on the server side and PostgreSQL as my database. So here's the problem, I'm trying to use the useEffect like this:
const [tweets, setTweets] = useState([]);
const getTweets = async () => {
const res = await api.get("/posts", {
headers: { token: localStorage.token },
});
setTweets(res.data);
};
useEffect(() => {
getTweets();
}, [tweets]);
I have no idea why it's looping infinite times, am I using it correctly though? I want the tweets to be updated every time I post a tweet. It's working fine but it's running infinite times. I just want it to re-render if a tweet got posted.
Here's my server code for getting all the posts:
async all(request: Request, response: Response, next: NextFunction) {
return this.postRepository.find({
relations: ["user"],
order: {
createdAt: "DESC",
},
});
}
The problem is every time you change the tweets it executes useEffect and changes the tweets and so long and so forth, so it's natural that it loops infinitely, the solution is to add a trigger that you set to true when a tweet gets posted, so the solution would be like this
const [tweets, setTweets] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const getTweets = async () => {
const res = await api.get("/posts", {
headers: { token: localStorage.token },
});
setTweets(res.data);
};
useEffect(() => {
getTweets();
setIsFetching(false);
}, [isFetching]);
and set some logic to use setIsFetching(true) in order to execute the useEffect
PS: if you use an empty array in useEffect, it would execute only when the component is mounted (at the start)
useEffect(() => {
getTweets();
}, [tweets]); // [tweets means that hook works every time 'tweets' state changes]
so your getTweets function set tweets => as tweets are changed hook works again => call getTweets => ... = infinite loop
if you want to download tweets, use empty array instead - hook will work once then
Pass empty array as a second arg for calling it once otherwise for changing it on every tweet change it will re-trigger, so whenever state will change only then it will be re-rendered like Tarukami explained. One thing you can do is check the length like mentioned below so not to compare the whole object but just the length
useEffect(() => {
getTweets();
}, [tweets.length]);
This might raise an error react-hooks/exhaustive-deps lint error (that's a bypass you can use it).
But if you want more tighter check you can compare the ids on each re-render (create a hash/key/id from all element in the array and compare them on each render) like so [tweet id here]) // Only re-subscribe if id changes

I cannot collect data from API using Axios + React

I'm beginner with React. I have 2 different cases where I'm using React Hooks which I cannot receive the data from my local API properly.
Case 1:
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data)
console.log(test)
})
...
}
It works with the state test displaying correctly the content from the API but I don't know why/how the Axios continues calling the API infinity - endless. (Ps: the very first call it returns undefined, then the next ones it works) What am I doing wrong?
To fix this I've tried to use useEffect like this (Case 2):
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
useEffect(() => {
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data)
console.log(test);
})
}, [])
...
}
Now the Axios works only once but no data is coming from the API. Maybe I should use async/wait for this case but I cannot make it work. Does anyone know how to fix that (Case 1 or/and Case 2)?
Thanks.
Updating the state is an asynchronous operation. So the state is not really updated until the next time the component gets rendered. If you want to capture the correct state, you can either console.log(res.data) or wrap that inside the useEffect hook with test as dependency.
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
// effect only runs when component is mounted
useEffect(() => {
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data);
});
}, []);
// effect runs whenever value of test changes
useEffect(() => {
console.log(test);
}, [test]);
}
That way it is guaranteed that the console.log runs when the value of test is updated.
Also the reason the API request is invoked once is you have not mentioned anything in the dependency array. [] empty dependency array runs the effect when the component is mounted for the first time.
async/await is just a wrapper around Promise object. So they would behave similarly.
The solution with useEffect is good. If you don't use it each render will call the request. This is the same if you put there console.log with any information. The reason why you don't see the data in the useEffect is that the value of the state is not updated in current render but in the next which is called by setter of the state. Move the console.log(test); after useEffect to see the data. On init it will be undefined but in the next render, it should contain the data from the request.

Resources