You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:Remix中如何让每次Submit获取最新Action返回结果

解决Remix中useSubmit返回Promise获取最新action结果的问题

核心修复方案

解决这个问题的关键是每次触发submit时创建全新的deferred实例,并通过navigation.key关联当前请求对应的actionData,确保只resolve本次submit的结果:

  • 移除对deferred的持久化,改为每次调用submit时创建新实例
  • 用ref存储当前活跃的deferred和请求key,避免组件重渲染丢失引用
  • 监听navigation状态,仅当本次请求完成时,用当前deferred resolve最新的actionData

修改后的完整代码

import type { ActionArgs, LoaderArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import type { SubmitOptions } from '@remix-run/react';
import { useActionData, useNavigation, useSubmit } from '@remix-run/react';
import { useCallback, useEffect, useRef } from 'react';

export function loader({ request }: LoaderArgs) {
  return json({ message: 'Hello World' });
}

export function action({ request }: ActionArgs) {
  let x = Math.random();
  console.log('generated ' + x);
  return json({ message: x });
}

export default function Index() {
  const submit = useSubmitPromise();

  const doSubmit = useCallback(
    (e: any) => {
      console.log('doSubmit');
      e.preventDefault();

      submit({ test: '123' }, { method: 'post', action: '?index' }).then(
        (data) => {
          console.log('result', data);
          alert('Resolved promise with:\n' + JSON.stringify(data, null, 2));
        }
      );
    },
    [submit]
  );

  return (
    <div>
      <h1>Welcome to Remix</h1>
      <button onClick={doSubmit}>useSubmitPromise</button>
    </div>
  );
}

// remix doesn't export this type
declare type SubmitTarget =
  | HTMLFormElement
  | HTMLButtonElement
  | HTMLInputElement
  | FormData
  | URLSearchParams
  | {
      [name: string]: string;
    }
  | null;

function useSubmitPromise() {
  const submit = useSubmit();
  const navigation = useNavigation();
  const actionData = useActionData();
  const currentDeferredRef = useRef<ReturnType<typeof deferred> | null>(null);
  const currentNavigationKeyRef = useRef<string | undefined>(undefined);

  useEffect(() => {
    // 仅当本次请求完成时,resolve对应的Promise
    if (
      navigation.state === 'idle' &&
      currentDeferredRef.current &&
      currentNavigationKeyRef.current === navigation.key
    ) {
      currentDeferredRef.current.resolve(actionData);
      // 重置ref,避免下次请求混淆
      currentDeferredRef.current = null;
      currentNavigationKeyRef.current = undefined;
    }
  }, [navigation.state, navigation.key, actionData]);

  const _submit = useCallback(
    (target: SubmitTarget, options: SubmitOptions = {}) => {
      // 为本次请求创建新的deferred
      const newDeferred = deferred();
      currentDeferredRef.current = newDeferred;
      currentNavigationKeyRef.current = navigation.key;
      
      submit(target, options);
      return newDeferred.promise;
    },
    [submit, navigation.key]
  );

  return _submit;
}

// create a *deferred* promise
function deferred() {
  let resolve: typeof Promise.resolve;
  let reject: typeof Promise.reject;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });

  return { resolve, reject, promise };
}

修复说明

  • 每次调用_submit都会生成独立的deferred,彻底避免Promise复用问题
  • 通过currentNavigationKeyRef匹配请求,确保resolve的是本次submit对应的actionData
  • 请求完成后重置ref,保证下一次submit的独立性,不会出现数据滞后

内容的提问来源于stack exchange,提问作者sinanspd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 14:52:27