I have an app where I am trying to make a network request upon component load to set a projectId using an API call and set the state via zustand. In addition, there are calls to listen to a web socket upgrade to update something on the frontend using the results.
For example,
const Component = () => {
const { setProjectIdStorage } = useProjectStore();
const projectIdStorage = useProjectStore((state) => state.projectIdStorage);
useEffect(() => {
createProject();
}, []);
function createProject() {
BackendClient.post(
`projects`,
{ project_id: projectIdStorage },
{ headers: { "Content-Type": "application/json" } },
)
.then((response) => {
setProjectIdStorage(response.data.project.id);
})
.catch((err) => {
console.log("Failed to create project");
});
}
useEffect(() => {
socket.on("server_recommendation", onServerRecommendation);
socket.on("server_code", onServerCode);
return () => {
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const refreshProjectStates = (projectId: string | null) => {
BackendClient.post(
`projects/project_state`,
{ project_id: projectId },
{ headers: { "Content-Type": "application/json" } },
).then(async (response: any) => {
const projectStates = response.data.project_states;
setProjectStates(projectStates);
if (projectStates.length > 0) {
setActiveProjectState(projectStates[projectStates.length - 1]);
}
});
};
const refreshRecommendations = (projectId: string | null) => {
BackendClient.get(
`recommendations?project_id=${projectId}`
).then((response) => {
setRecommendations(response.data.recommendations);
})
}
const onServerCode = () => {
setLoading(false);
refreshProjectStates(projectIdStorage);
setPrompt("");
};
const onServerRecommendation = () => {
refreshRecommendations(projectIdStorage);
};
}
The problem I am having is that after the first network request of CreateProject() AND the projectID is set - the projectId when accessed further down in the refreshProjectState/refreshRecommendations are always NULL. However, it seems to work only after the page is refreshed.
I am using Zustand in this example but it seems to fail with regular react useState() as well.
Both onServerCode
and onServerRecommendation
have a stale closure over the initial projectIdStorage
state that is selected.
Update the useEffect
hook to use a dependency on the projectIdStorage
value so that the server event handlers have the current project id value.
Example:
const projectIdStorage = useProjectStore((state) => state.projectIdStorage);
useEffect(() => {
// Disconnect/close socket on component unmount
return () => {
socket.disconnect();
};
}, []);
useEffect(() => {
const refreshProjectStates = (projectId: string | null) => {
BackendClient.post(
`projects/project_state`,
{ project_id: projectId },
{ headers: { "Content-Type": "application/json" } },
)
.then(async (response: any) => {
const projectStates = response.data.project_states;
setProjectStates(projectStates);
if (projectStates.length > 0) {
setActiveProjectState(projectStates[projectStates.length - 1]);
}
});
};
const refreshRecommendations = (projectId: string | null) => {
BackendClient.get(`recommendations?project_id=${projectId}`)
.then((response) => {
setRecommendations(response.data.recommendations);
});
};
const onServerCode = () => {
setLoading(false);
refreshProjectStates(projectIdStorage);
setPrompt("");
};
const onServerRecommendation = () => {
refreshRecommendations(projectIdStorage);
};
socket.on("server_recommendation", onServerRecommendation);
socket.on("server_code", onServerCode);
return () => {
socket.off("server_recommendation", onServerRecommendation);
socket.off("server_code", onServerCode);
};
}, [projectIdStorage]);