Archived course lesson

Add Delete Recipe Button

Part of Frontend Serverless with React and GraphQL

Objective: Allow a user to delete their own recipes

We will now create a delete recipe button. How it will work is that when the button is clicked, it will pop up a modal dialog box and ask for them to confirm the delete. If they press confirm it will run a delete mutation and then after it finishes, we will re-direct them back to the my-recipes page.

Everything is pretty similar to the other mutations we’ve used except for the modal. How the modal works is it is a separate react component that sits next to the button in a hidden state. We create functions called handleHide and handleShow which will toggle a state variable called isModalVisible to be true or false. We set the visibility of the modal based on that state variable.

import { Button, Modal } from 'antd';
import { useMutation } from '@apollo/react-hooks';
import { deleteRecipeGraphQL } from '../graphql/mutations/deleteRecipe';
import { useState } from 'react';
import { recipesGraphQL } from '../graphql/queries/recipes';
import Router from 'next/router';

export const DeleteButton = ({
  id,
  disabled,
}: {
  id: string;
  disabled: boolean;
}) => {
  const [deleteRecipeMutation, { loading: deleteRecipeLoading }] = useMutation(
    deleteRecipeGraphQL,
  );

  const [isModalVisible, setModalVisibility] = useState(false);

  const handleOk = async () => {
    if (!deleteRecipeLoading) {
      await deleteRecipeMutation({
        refetchQueries: [{ query: recipesGraphQL }],
        variables: {
          where: { id },
        },
      });
    }

    setModalVisibility(false);
    Router.replace('/my-recipes');
  };
  const handleShow = () => setModalVisibility(true);
  const handleHide = () => setModalVisibility(false);

  return (
    <>
      <Button block type="danger" disabled={disabled} onClick={handleShow}>
        Delete Recipe
      </Button>
      <Modal
        title="Confirm Delete"
        visible={isModalVisible}
        onOk={handleOk}
        onCancel={handleHide}
      >
        <p>Are you sure that you want to delete this recipe?</p>
      </Modal>
    </>
  );
};

Now that we’ve created the button, let’s import it into the <UpdateRecipe> component. We will make sure to disable it either when the recipe query itself is loading or when the delete mutation is currently firing to prevent accidental double clicking of the delete button.

import { useQuery, useMutation } from '@apollo/react-hooks';
import { recipeGraphQL } from '../graphql/queries/recipe';
import { submitForm } from '../utils/submitForm';
import { useState } from 'react';
import * as _ from 'lodash';
import { Form, Row, Col, Button } from 'antd';
import { GenerateInput, GenerateTextInput } from './GenerateFields';
import { GenerateIngredients } from './GenerateIngredients';
import { Loading } from './notify/Loading';
import { createUpdateObj } from '../utils/createUpdateObj';
import { updateRecipeGraphQL } from '../graphql/mutations/updateRecipe';
import { DeleteButton } from './DeleteButton';

export const UpdateRecipe = ({ id }) => {
  const { loading: isQueryLoading, data, error } = useQuery(recipeGraphQL, {
    variables: { where: { id } },
  });
  const [updateRecipeMutation, { loading: updateRecipeLoading }] = useMutation(
    updateRecipeGraphQL,
  );

  const [recipeState, setRecipeState] = useState({ isQueryLoading: true });

  const initiateUpdateRecipe = () => {
    const updateObj = createUpdateObj(data, inputs);
    return updateRecipeMutation({
      refetchQueries: [{ query: recipeGraphQL, variables: { where: { id } } }],
      variables: {
        data: {
          ...updateObj,
        },
        where: { id },
      },
    });
  };

  const {
    inputs,
    handleInputChange,
    handleAddIngredient,
    handleDeleteIngredient,
    handleDropdownChange,
    handleUpdate,
    setInputs,
  } = submitForm(
    {
      title: '',
      description: '',
      content: '',
      ingredients: [],
    },
    initiateUpdateRecipe,
  );
  if (!isQueryLoading && recipeState.isQueryLoading) {
    const { __type, ...loadedRecipe } = _.get(data, 'recipe');
    setInputs((state) => ({ ...state, ...loadedRecipe }));
    setRecipeState((state) => ({ ...state, isQueryLoading }));
  }

  if (!data) return <Loading />;

  const disabled = isQueryLoading || updateRecipeLoading;

  return (
    <Form onFinish={handleUpdate}>
      <GenerateInput
        name="title"
        value={inputs.title}
        handleInputChange={handleInputChange}
      />
      <GenerateInput
        name="description"
        value={inputs.description}
        handleInputChange={handleInputChange}
      />
      <GenerateTextInput
        name="content"
        value={inputs.content}
        handleInputChange={handleInputChange}
      />
      <GenerateIngredients
        names={['amount', 'unit', 'type']}
        values={inputs.ingredients}
        handleAddIngredient={handleAddIngredient}
        handleDeleteIngredient={handleDeleteIngredient}
        handleInputChange={handleInputChange}
        handleDropdownChange={handleDropdownChange}
      />
      <Row>
        <Col span={16} />
        <Col span={4}>
          <Form.Item label="Action">
            <Button block disabled={disabled} type="primary" htmlType="submit">
              Update Recipe
            </Button>
            <DeleteButton id={id} disabled={disabled} />
          </Form.Item>
        </Col>
      </Row>
    </Form>
  );
};