为什么我刷新页面时没有显示来自 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
,我就得到了相应的食谱、配料和步骤。
接下来我把菜谱的标题、菜谱的描述、菜谱的成分和步骤放入四个变量中:originalTitle
、originalDescription
、originalIngredients
和originalSteps
,分别。
接下来我用 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 页面后,originalTitle
、originalDescripion
、originalIngredients
和 originalSteps
要么是空字符串,要么是空数组。这是由于 ||
运算符附加到每个变量。
提前感谢您的任何反馈和帮助。
如果您需要什么,请告诉我。
问题是您使用的挂钩 useRecipeQuery
会在将来的某个时间点 return 数据,并且您的表单有第二个挂钩 useState
依赖于这个数据。这意味着当 React 将呈现此组件时,useRecipeQuery
将 return 没有数据(因为它仍在获取)因此用于表单的 useState
钩子被初始化为空数据。一旦 useRecipeQuery
完成获取,它将重新评估此代码,但这对表单的 useState
挂钩没有任何影响,因为它已经初始化并已在内部缓存其状态。它在一种情况下对您有用,但在另一种情况下不起作用的原因是,在一种情况下,您的 useRecipeQuery
立即 return 缓存中可用的数据,而在另一种情况下,它需要执行实际获取它。
解决方法是什么?
- 假设您在首次加载此组件时没有可供表单正确呈现的数据。所以用一些可接受的空状态初始化你的表单。
- 使用
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 ]);
我正在使用 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
,我就得到了相应的食谱、配料和步骤。
接下来我把菜谱的标题、菜谱的描述、菜谱的成分和步骤放入四个变量中:originalTitle
、originalDescription
、originalIngredients
和originalSteps
,分别。
接下来我用 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 页面后,originalTitle
、originalDescripion
、originalIngredients
和 originalSteps
要么是空字符串,要么是空数组。这是由于 ||
运算符附加到每个变量。
提前感谢您的任何反馈和帮助。 如果您需要什么,请告诉我。
问题是您使用的挂钩 useRecipeQuery
会在将来的某个时间点 return 数据,并且您的表单有第二个挂钩 useState
依赖于这个数据。这意味着当 React 将呈现此组件时,useRecipeQuery
将 return 没有数据(因为它仍在获取)因此用于表单的 useState
钩子被初始化为空数据。一旦 useRecipeQuery
完成获取,它将重新评估此代码,但这对表单的 useState
挂钩没有任何影响,因为它已经初始化并已在内部缓存其状态。它在一种情况下对您有用,但在另一种情况下不起作用的原因是,在一种情况下,您的 useRecipeQuery
立即 return 缓存中可用的数据,而在另一种情况下,它需要执行实际获取它。
解决方法是什么?
- 假设您在首次加载此组件时没有可供表单正确呈现的数据。所以用一些可接受的空状态初始化你的表单。
- 使用
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 ]);