reactjsreact-query

Updating React 18 app to createRoot breaks mutations in TanStack/query


I use TanStack/query to handle mutations for registration/login to a Drupal 9 website.

If registration is successful, I automatically log the user in.

With React 17, the following code allows the user to register and then immediately logs the user in. However, with React 18 and createRoot, registration succeeds but login fails because onSuccess no longer has access to the variable formData (console.log() in onSuccess returns an empty object for formData).

const onSubmit = (submitData: DrupalLoginFormInput) => {
  setIsLoading(true);
  mutateRegister.mutate(submitData);
};

export const useMutationRegister = (
  formData: DrupalLoginFormInput,
  setIsLoading: React.Dispatch<React.SetStateAction<boolean>>,
): UseMutationResult<HttpResponse, string, DrupalLoginFormInput> => {
  const mutateLogin = useMutationLogin();
  return useMutation(
    (registerData: DrupalLoginFormInput) =>
      postRegisterUser(registerData, platform),
    {
      onSuccess: () => {
        // console.log('mutateRegister succeeded', response, formData);
        // Log in with the newly registered info.
        const registeredDataToLogin = {
          mail: formData.mail,
          pass: formData.pass,
        };
        mutateLogin.mutate(registeredDataToLogin);
      },
    },
  );
};

export type DrupalLoginFormInput = {
  mail: string;
  pass: string;
};

The React 18 batching deep dive suggests using flushSync() to disable batching, so I tried this:

const onSubmit = (submitData: DrupalLoginFormInput) => {
  flushSync(() => {
    setIsLoading(true);
    mutateRegister.mutate(submitData);
  });
};

However, login is still broken (formData is not available in onSuccess).

If I keep my app with React 18 but revert createRoot() to the old way (render(<App />, container);), then login works again. However, I want to use createRoot() because that unlocks the new React 18 features.

How can I fix my code so that the formData variable doesn't get removed before I access it in onSuccess?


Solution

  • I was using flushSync() incorrectly. You need to call flushSync() each time you want the DOM to update.

    Bad code:

    const onSubmit = (submitData: DrupalLoginFormInput) => {
      flushSync(() => {
        setIsLoading(true);
        mutateRegister.mutate(submitData);
      });
    };
    

    Good code, state updates correctly:

    const onSubmit = (submitData: DrupalLoginFormInput) => {
      flushSync(() => {
        setIsLoading(true);
      });
      flushSync(() => {
        mutateRegister.mutate(submitData);
      });
    };