升级React 18至createRoot导致TanStack/query突变失效求助
我使用TanStack/Query处理Drupal 9网站的注册/登录逻辑,原本设计为注册成功后自动为用户登录。在React 17中以下代码可正常完成注册并立即登录,但升级到React 18并使用createRoot后,注册成功但登录失败——onSuccess回调中无法访问formData变量,打印显示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; };
我曾参考React 18批处理相关文档尝试用flushSync禁用批处理:
const onSubmit = (submitData: DrupalLoginFormInput) => { flushSync(() => { setIsLoading(true); mutateRegister.mutate(submitData); }); };
但登录仍失效(onSuccess中仍无法获取formData)。若保留React 18但换回旧的render(<App />, container)方式则登录恢复正常,但我希望使用createRoot以解锁React 18新特性,请问如何修改代码确保onSuccess访问formData时变量不丢失?
解决方案
问题根源在于闭包捕获的变量过期:React 18的并发渲染机制下,onSuccess回调捕获的是组件渲染时的formData旧值,而非注册时传入的最新数据。正确的做法是直接使用TanStack Query提供的mutation触发变量,而非依赖外部传入的formData。
修改后的代码
- 调整
useMutationRegister,移除不必要的formData参数,在onSuccess中通过第二个参数获取注册时的原始数据:
export const useMutationRegister = ( setIsLoading: React.Dispatch<React.SetStateAction<boolean>>, ): UseMutationResult<HttpResponse, string, DrupalLoginFormInput> => { const mutateLogin = useMutationLogin(); return useMutation( (registerData: DrupalLoginFormInput) => postRegisterUser(registerData, platform), { onSuccess: (_, variables) => { // 使用mutation触发时传入的原始变量,而非外部formData const registeredDataToLogin = { mail: variables.mail, pass: variables.pass, }; mutateLogin.mutate(registeredDataToLogin); }, onSettled: () => { // 注册请求完成(无论成功失败)后更新加载状态 setIsLoading(false); } }, ); };
onSubmit函数保持不变:
const onSubmit = (submitData: DrupalLoginFormInput) => { setIsLoading(true); mutateRegister.mutate(submitData); };
原理说明
TanStack Query的useMutation中,onSuccess回调的第二个参数variables就是触发mutation时传入的原始数据,它由Query内部保存,不受React组件渲染闭包的影响。直接使用这个变量就能确保拿到的是注册时的准确账号密码,彻底避免闭包导致的变量丢失问题。
之前尝试的flushSync无效,是因为问题本质不是React的批处理机制,而是闭包捕获的变量过期,所以禁用批处理无法解决根本问题。
内容的提问来源于stack exchange,提问作者Patrick Kenny

