Search code examples
reactjsreact-reduxzustand

State Variable is Not Updated in React Const Function


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.


Solution

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