为什么我刷新页面时没有显示来自 apollo 服务器的数据?

Why is my data that is coming from apollo server not showing up when I refresh the page?

我正在使用 React、Apollo 和 React Router 构建一个简单的应用程序。此应用程序允许您创建食谱,以及编辑和删除它们(您的标准 CRUD 网站)。

我考虑过如何呈现我的问题,我认为最好的方式是视觉化。

这是主页 (localhost:3000):

当你点击菜谱的标题时,你会看到 (localhost:3000/recipe/15):

如果你点击主页上的'create recipe'按钮,你会看到这个(localhost:3000/create-recipe):

如果您在主页上点击食谱上的删除按钮,您会看到 (localhost:3000):

如果你在主页上点击食谱的编辑按钮,你会看到这个 (localhost:3000/recipe/15/update):

这个更新表单就是问题的起点。如您所见,表格中填满了配方的旧值。一切都在计划之中。但是,当我刷新页面时,这是你看到的:

一片空白。我 67% 确定这与 React 渲染组件的方式或我查询我的 apollo 服务器的方式有关。我不完全理解 React 渲染组件的过程。

这是 UpdateRecipe 页面的代码(您可能一直在等待):

import React, { useState } from "react";
import { Button } from "@chakra-ui/react";
import {
  useUpdateRecipeMutation,
  useRecipeQuery,
  useIngredientsQuery,
  useStepsQuery,
} from "../../types/graphql";
import { useNavigate, useParams } from "react-router-dom";
import { SimpleFormControl } from "../../shared/SimpleFormControl";
import { MultiFormControl } from "../../shared/MultiFormControl";

interface UpdateRecipeProps {}

export const UpdateRecipe: React.FC<UpdateRecipeProps> = ({}) => {
  let { id: recipeId } = useParams() as { id: string };

  const intRecipeId = parseInt(recipeId);
  const { data: recipeData } = useRecipeQuery({
    variables: { id: intRecipeId },
  });

  const { data: ingredientsData } = useIngredientsQuery({
    variables: { recipeId: intRecipeId },
  });

  const { data: stepsData } = useStepsQuery({
    variables: { recipeId: intRecipeId },
  });

  const originalTitle = recipeData?.recipe.recipe?.title || "";
  const originalDescription = recipeData?.recipe.recipe?.description || "";
  const originalIngredients =
    ingredientsData?.ingredients?.ingredients?.map((ing) => ing.text) || [];
  const originalSteps = stepsData?.steps?.steps?.map((stp) => stp.text) || [];

  const [updateRecipe] = useUpdateRecipeMutation();
  const navigate = useNavigate();

  const [formValues, setFormValues] = useState({
    title: originalTitle,
    description: originalDescription,
    ingredients: originalIngredients,
    steps: originalSteps,
  });
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
      }}
    >
      <SimpleFormControl
        label="Title"
        name="title"
        type="text"
        placeholder="Triple Chocolate Cake"
        value={formValues.title}
        onChange={(e) => {
          setFormValues({ ...formValues, title: e.target.value });
        }}
      />
      <SimpleFormControl
        label="Description"
        name="description"
        type="text"
        placeholder="A delicious combination of cake and chocolate that's bound to mesmerize your tastebuds!"
        value={formValues.description}
        onChange={(e) => {
          setFormValues({ ...formValues, description: e.target.value });
        }}
      />
      <MultiFormControl
        label="Ingredients"
        name="ingredients"
        type="text"
        placeholder="Eggs"
        values={formValues.ingredients}
        onAdd={(newValue) => {
          setFormValues({
            ...formValues,
            ingredients: [...formValues.ingredients, newValue],
          });
        }}
        onDelete={(_, index) => {
          setFormValues({
            ...formValues,
            ingredients: formValues.ingredients.filter(
              (__, idx) => idx !== index
            ),
          });
        }}
      />
      <MultiFormControl
        ordered
        label="Steps"
        name="steps"
        type="text"
        placeholder="Pour batter into cake tray"
        color="orange.100"
        values={formValues.steps}
        onAdd={(newValue) => {
          setFormValues({
            ...formValues,
            steps: [...formValues.steps, newValue],
          });
        }}
        onDelete={(_, index) => {
          setFormValues({
            ...formValues,
            steps: formValues.steps.filter((__, idx) => idx !== index),
          });
        }}
      />
      <Button type="submit">Update Recipe</Button>
    </form>
  );
};

我会尽量解释清楚。

首先,我从 url 中获取 id 参数。有了这个id,我就得到了相应的食谱、配料和步骤。

接下来我把菜谱的标题、菜谱的描述、菜谱的成分和步骤放入四个变量中:originalTitleoriginalDescriptionoriginalIngredientsoriginalSteps,分别。

接下来我用 useState() 设置了一些状态,叫做 formValues。它看起来像这样:

{
    title: originalTitle,
    description: originalDescription,
    ingredients: originalIngredients,
    steps: originalSteps,
}

最后,我 return 一个 form 其中包含 4 个组件:

第一个组件是 SimpleFormControl,它用于标题。请注意我如何将此组件的 value 属性设置为 formValues.title.

第二个组件也是 SimpleFormControl,用于描述,其中 value 属性设置为 formValues.description

第三个组件是 MultiFormControl,它是用于配料的。该组件的 value 属性设置为 formValues.ingredients

第四部分也是一个MultiFormControl,是为了台阶。该组件的 value 属性设置为 formValues.steps

如果您需要查看这两个组件的代码,请告诉我。

注:

当我通过主页进入 UpdateRecipe 页面时,它运行良好。刷新 UpdateRecipe 页面后,originalTitleoriginalDescripionoriginalIngredientsoriginalSteps 要么是空字符串,要么是空数组。这是由于 || 运算符附加到每个变量。

提前感谢您的任何反馈和帮助。 如果您需要什么,请告诉我。

问题是您使用的挂钩 useRecipeQuery 会在将来的某个时间点 return 数据,并且您的表单有第二个挂钩 useState 依赖于这个数据。这意味着当 React 将呈现此组件时,useRecipeQuery 将 return 没有数据(因为它仍在获取)因此用于表单的 useState 钩子被初始化为空数据。一旦 useRecipeQuery 完成获取,它将重新评估此代码,但这对表单的 useState 挂钩没有任何影响,因为它已经初始化并已在内部缓存其状态。它在一种情况下对您有用,但在另一种情况下不起作用的原因是,在一种情况下,您的 useRecipeQuery 立即 return 缓存中可用的数据,而在另一种情况下,它需要执行实际获取它。

解决方法是什么?

  1. 假设您在首次加载此组件时没有可供表单正确呈现的数据。所以用一些可接受的空状态初始化你的表单。
  2. 使用 useEffect 连接你的钩子,这样当 useRecipeQuery 完成加载数据时,它会相应地更新你的表单状态。
  const { loading, data: recipeData } = useRecipeQuery({
    variables: { id: intRecipeId },
  });

  const [formValues, setFormValues] = useState({
    title: "",
    description: "",
    ingredients: [],
    steps: [],
  });

  useEffect(() => {
    if (!loading && recipeData ) {
      setFormValues({
        title: recipeData?.recipe.recipe?.title,
        description: recipeData?.recipe.recipe?.description,
        ingredients: ingredientsData?.ingredients?.ingredients?.map((ing) => ing.text),
        steps:  stepsData?.steps?.steps?.map((stp) => stp.text),
      });
    }
  }, [loading, recipeData ]);