网站搜索

reactjs - 在使用 defer 时,useLoaderData() 返回的是之前获取的结果,而不是一个 promise


我在使用 useLoaderData() 钩子时遇到了问题,当使用 react-router 的 Form 组件并设置为 GET 方法时。我已经编程让 loader 返回延迟数据,并且在我的组件中使用 Suspense 来在 promise 解析之前渲染一个加载图标。这在初次渲染时按预期工作,即:

  • 装载机运行。
  • 组件渲染并运行 Suspense 回退。
  • 数据已从 API 获取
  • Promise 解决了,Await 中的回调函数运行。

然而,当我再次提交表格时,会发生以下情况:

  • 装载机运行
  • 组件渲染了,但这次 useLoaderData() 返回的是之前获取的结果,而不是 Promise
  • Await 中的回调函数会运行(因为没有要解决的 Promise)。
  • 数据是从 API 获取的。(直到这一点 URL 才会切换)
  • 组件重新渲染,useLoaderData() 返回新获取的数据。
  • Await 中的回调函数会重新运行。

应用组件(路由器):

function App() {
  const browserRouter = createBrowserRouter(
    createRoutesFromElements(
      <Route
        path="/"
        element={<HomePage />}
        loader={HomePageLoader}
      />
    )
  );

  return (
    <>
      <Navbar />
      <RouterProvider router={browserRouter} />
    </>
  );
}

与 API 交互的函数()

export async function fetchDataFromColorAPI(targetUrl: string) {
  const response = await fetch(targetUrl);
  if (response.status !== 200) {
    throw new Error("Error occurred with API");
  }
  const data = await response.json();
  console.log("API data:  ",data.colors);
  return getColorsInfo(data.colors);
}

加载器函数:

export function loader({ request }: LoaderFunctionArgs) {
  console.log("in loader");
  const params = new URL(request.url).searchParams;
  const { color, mode, count } = getSearchParams(params); //utility function to get search parameters
  const baseUrl = "https://www.thecolorapi.com/scheme";
  const searchQuery = `?hex=${color}&mode=${mode}&count=${count}&format=json`;

  return defer({ colorsInfo: fetchDataFromColorAPI(baseUrl + searchQuery) });
}

调用 useLoaderData() 的组件:

export default function HomePage() {
  console.log("In component");

  const data = useLoaderData() as LoaderData;
  function renderPallet(colorsInfo: Color[]) {
    console.log("promise resolved, Values recieved: ", colorsInfo);
    return <Pallet colorsInfo={colorsInfo} />;
  }

  return (
    <main>
      <InputForm />
      <Suspense fallback={<h1>Loading....</h1>}>
        <Await resolve={data.colorsInfo}>{renderPallet}</Await>
      </Suspense>
    </main>
  );
}

表格:

<Form  replace className="form">
  //Input fields
</Form>

我尝试过查看文档,但找不到原因。我的猜测是,这可能是由于 React 或 React-Router 进行了一些优化所致。有人能具体解释这种行为吗,特别是当 loader 和 useLoaderData() 与 React-Router 的 Form “GET” 方法结合使用时的表现。如何才能强制 useLoaderData() 钩子返回一个 promise,而不是之前获取的结果。

GitHub 仓库: https://github.com/Azfar731/ColorPicker.git

答案#1

一旦路线被加载,它不会重新加载,除非你先导航到别的地方。换句话说,一旦路线第一次加载,任何额外的操作只能重新渲染已经挂载的路线组件并重新运行任何路线加载器,而不是进行完整的加载/挂载周期。

在调用路由操作后,数据将自动重新验证并返回加载器的最新结果。

请注意,useLoaderData 不会发起请求。它只是读取 React Router 内部管理的请求结果,所以当组件因路由以外的原因重新渲染时,你不需要担心它会重新获取数据。

这也意味着返回的数据在多次渲染之间是稳定的,因此你可以安全地将其传递给 React 钩子中的依赖数组,例如 useEffect。它只会在操作或某些导航后再次调用加载器时更改。在这些情况下,身份会发生变化(即使值没有变化)。

这与您观察到的情况相符,即之前获取的结果仍然提供给组件,直到它被下一次获取的结果替换。

我认为你可以使用 useNavigation 钩子来处理在路由或表单操作被触发时路由的加载器正在获取数据的情况。请具体参阅 navigation.state 以获取完整细节。

示例:

import { ...., useNavigation } from 'react-router-dom';

export default function HomePage() {
  const data = useLoaderData() as LoaderData;
  const navigation = useNavigation();

  // Are we reloading after an action?
  const isReloading =
    navigation.state === "loading" &&
    navigation.formData != null &&
    navigation.formAction === navigation.location.pathname;

  // Are we redirecting after an action?
  const isRedirecting =
    navigation.state === "loading" &&
    navigation.formData != null &&
    navigation.formAction !== navigation.location.pathname;

  function renderPallet(colorsInfo: Color[]) {
    console.log("promise resolved, Values received: ", colorsInfo);
    return <Pallet colorsInfo={colorsInfo} />;
  }

  return (
    <main>
      {(isReloading || isRedirecting) && <h1>Loading....</h1>}
      <InputForm />
      <Suspense fallback={<h1>Loading....</h1>}>
        <Await resolve={data.colorsInfo}>{renderPallet}</Await>
      </Suspense>
    </main>
  );
}