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

Time:11-19

I am building a simple application using React, Apollo and React Router. This application allows you to create recipes, as well as edit and delete them (your standard CRUD website).

I thought about how I would present my problem, and I figured the best way was visually.

Here is the home page (localhost:3000):

enter image description here

When you click on the title of a recipe, this is what you see (localhost:3000/recipe/15):

enter image description here

If you click the 'create recipe' button on the home page, this is what you see (localhost:3000/create-recipe):

enter image description here

If you click on the delete button on a recipe on the home page, this is what you see (localhost:3000):

enter image description here

If you click on the edit button on a recipe on the home page, this is what you see (localhost:3000/recipe/15/update):

enter image description here

This update form is where the problem begins. As you can see, the form has been filled with the old values of the recipe. Everything is going to plan. But, when I refresh the page, this is what you see:

enter image description here

It's all blank. I am 67% sure this is something to do with the way React renders components or the way I am querying my apollo server. I don't fully understand the process React goes through to render a component.

Here is the code for the UpdateRecipe page (what you've probably been waiting for):

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>
  );
};

I'll try to explain it as best as I can.

First I get the id parameter from the url. With this id, I grab the corresponding recipe, its ingredients and its steps.

Next I put the title of the recipe, the description of the recipe, the ingredients of the recipe and the steps into four variables: originalTitle, originalDescription, originalIngredients and originalSteps, respectively.

Next I set up some state with useState(), called formValues. It looks like this:

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

Finally, I return a form which contains 4 component:

The first component is a SimpleFormControl and it is for the title. Notice how I set the value prop of this component to formValues.title.

The second component is also a SimpleFormControl and it is for the description, which has a value prop set to formValues.description.

The third component is a MultiFormControl and it's for the ingredients. This component has its value props set to formValues.ingredients.

The fourth component is also aMultiFormControl and it's for the steps. This component has its value props set to formValues.steps.

Let me know if you need to see the code for these two components.

Note:

When I come to the UpdateRecipe page via the home page, it works perfectly. As soon as I refresh the UpdateRecipe page, the originalTitle, originalDescripion, originalIngredients and originalSteps are either empty strings or empty arrays. This is due to the || operator attached to each variable.

Thanks in advance for any feedback and help. Let me know if you need anything.

CodePudding user response:

The problem is that you are using one hook useRecipeQuery that will return data at some point in the future and you have a second hook useState for your form that relies on this data. This means that when React will render this component the useRecipeQuery will return no data (since it's still fetching) so the useState hook used for your form is initialized with empty data. Once useRecipeQuery is done fetching it will reevaluate this code, but that doesn't have any effect on the useState hook for your form, since it's already initialized and has internally cached its state. The reason why it's working for you in one scenario, but not in the other, is that in one scenario your useRecipeQuery immediately returns the data available from cache, whereas in the other it needs to do the actual fetch to get it.

What is the solution?

  1. Assume you don't have the data available for your form to properly render when you first load this component. So initialize your form with some acceptable empty state.
  2. Use useEffect to wire your hooks, so that when useRecipeQuery finishes loading its data, it'll update your form state accordingly.
  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 ]);
  • Related