End to End React with Prisma 2

Start at the beginning and work your way through this project. The code for each step as well as the finished project can be found in the Github repository.


    26. Add delete button

    Objective: Create a delete button for deleting feeds and bundles.

    Now that we can create and update feeds and bundles, let's work on the delete button. Let's first build out the graphical elements and then we'll hook it all up to the mutations to make it work.

    components/itemDelete.tsx

    tsx

    import { BundleObject, FeedObject, ItemType } from '../utils/types'
    import { Delete, Spin } from './svg'
    export const ItemDelete = ({
    item,
    type,
    }: {
    item: FeedObject | BundleObject
    type: ItemType
    }) => {
    return (
    <>
    <div className="flex col-span-1 py-2 px-1 z-10">
    {false ? (
    <Spin className="h-6 w-6 text-gray-500 animate-spin" />
    ) : (
    <Delete className="h-6 w-6 text-red-500" />
    )}
    </div>
    </>
    )
    }

    Then add Delete svg to the svg file:

    components/svg.tsx

    tsx

    export const Delete = ({ className }) => (
    <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke="currentColor"
    className={className}
    >
    <path
    strokeLinecap="round"
    strokeLinejoin="round"
    strokeWidth={2}
    d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
    />
    </svg>
    )

    Then we will use this ItemDelete component in the OneListItem component right next to the ItemEdit component that we've already made.

    components/oneListItem.tsx

    tsx

    import { ItemDelete } from './itemDelete'
    // more of the react component
    ;<div className="col-span-2 flex justify-end">
    {canManipulate ? (
    <ItemEdit
    item={item}
    type={type}
    selected={selected}
    setSelected={setSelected}
    />
    ) : null}
    {canManipulate ? <ItemDelete item={item} type={type} /> : null}
    </div>

    Now we should see a delete button but it doesn't work yet. The operation of the button will be as follows: a user clicks on the button, it pops up a modal dialog box which prompts them to confirm that they want to delete the item in question. If they click confirm it gets deleted otherwise if they click cancel, the dialog box goes away with no change made.

    Let's now build the dialog button. We'll use a state react hook for managing the boolean that controls whether it is showing or not. We'll also take advantage of some lovely styling with Tailwind to make sure this modal looks in tip-top shape. You'll see a lot of classes set for a variety of divs and buttons, but this is only for visually setting all the details- it is all cosmetic.

    components/itemDelete.tsx

    tsx

    import { useState } from 'react'
    import { BundleObject, FeedObject, ItemType } from '../utils/types'
    import { Delete, Spin } from './svg'
    export const ItemDelete = ({
    item,
    type,
    }: {
    item: FeedObject | BundleObject
    type: ItemType
    }) => {
    const isFeed = type === ItemType.FeedType
    const __typename = isFeed ? 'Feed' : 'Bundle'
    const [modalVisibility, setVisibility] = useState(false)
    return (
    <>
    {modalVisibility ? (
    <div className="fixed z-20 inset-0 overflow-y-auto">
    <div className="flex items-end justify-center min-h-screen px-4 pb-20 text-center sm:block sm:p-0">
    <div className="fixed inset-0 transition-opacity">
    <div className="absolute inset-0 bg-gray-500 opacity-75"></div>
    <span className="hidden sm:inline-block sm:align-middle sm:h-screen"></span>
    <div
    className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
    role="dialog"
    aria-modal="true"
    aria-labelledby="modal-headline"
    >
    <div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
    <h3 className="text-lg font-medium">
    Are you sure you want to delete this{' '}
    {isFeed ? 'feed?' : 'bundle?'}
    </h3>
    </div>
    <div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
    <span className="flex w-full rounded-md sm:ml-3 sm:w-auto">
    <button
    onClick={e => {
    e.preventDefault()
    setVisibility(false)
    }}
    type="button"
    className="font-bold py-2 px-6 rounded text-white bg-red-500 text-white w-full"
    >
    Delete
    </button>
    </span>
    <span className="mt-3 flex w-full rounded-md sm:mt-0 sm:w-auto">
    <button
    onClick={e => {
    e.preventDefault()
    setVisibility(false)
    }}
    type="button"
    className="font-bold py-2 px-6 rounded bg-white text-gray-700 rounded border-4 border-gray-300 w-full"
    >
    Cancel
    </button>
    </span>
    </div>
    </div>
    </div>
    </div>
    </div>
    ) : null}
    <div
    onClick={e => {
    e.preventDefault()
    setVisibility(true)
    }}
    className="flex col-span-1 py-2 px-1 z-10"
    >
    {false ? (
    <Spin className="h-6 w-6 text-gray-500 animate-spin" />
    ) : (
    <Delete className="h-6 w-6 text-red-500" />
    )}
    </div>
    </>
    )
    }

    You can see that the div which has the Delete component now has an onClick handler which will set the visibility to true. If it is true, then we show the whole modal dialog box which has two buttons on it Delete and Cancel. Right now either button will set the visibility to false which makes the dialog box go away- but we will now load down the Delete button with a mutation that will actuall execute the mutation.

    The first step is to add a deleteItemMutation handler. We also want to use the useFetchUser hook so we can be sure that we won't let someone click the delete button if they aren't a user or if their information is loading.

    components/itemDelete.tsx

    tsx

    import { useMutation } from '@apollo/client';
    import { useState } from 'react';
    import { DELETE_BUNDLE_MUTATION, DELETE_FEED_MUTATION } from '../utils/api/graphql/mutations';
    import { BUNDLES_QUERY, FEEDS_QUERY } from '../utils/api/graphql/queries';
    import { BundleObject, FeedObject, ItemType } from '../utils/types';
    import { useFetchUser } from '../utils/user';
    import { Delete, Spin } from './svg';
    export const ItemDelete = ({ item, type }: { item: FeedObject | BundleObject; type: ItemType }) => {
    const isFeed = type === ItemType.FeedType;
    const [modalVisibility, setVisibility] = useState(false);
    const [deleteItemMutation, { loading: deleteItemLoading }] = useMutation(isFeed ? DELETE_FEED_MUTATION : DELETE_BUNDLE_MUTATION);
    const { user, loading } = useFetchUser();
    // more of the react component
    <button
    onClick={e => {
    e.preventDefault();
    deleteItemMutation({
    variables: {
    data: {
    id: item.id,
    },
    },
    optimisticResponse: {
    __typename: 'Mutation',
    [`delete${__typename}`]: {
    id: item.id,
    __typename,
    },
    },
    update: (store, { data: { deleteFeed, deleteBundle } }) => {
    try {
    const data = store.readQuery({ query: isFeed ? FEEDS_QUERY : BUNDLES_QUERY });
    const currentItems = data[isFeed ? 'feeds' : 'bundles'];
    const deleteItem = isFeed ? deleteFeed : deleteBundle;
    const updatedArray = currentItems.filter(o => o.id !== deleteItem.id);
    const newData = { [isFeed ? 'feeds' : 'bundles']: updatedArray };
    store.writeQuery({ query: isFeed ? FEEDS_QUERY : BUNDLES_QUERY, data: newData });
    } catch (e) {}
    },
    });
    setVisibility(false);
    }}
    type="button"
    className="font-bold py-2 px-6 rounded text-white bg-red-500 text-white w-full"
    >
    Delete
    </button>
    // more of the react component
    {deleteItemLoading || loading || !user ? (
    <Spin className="h-6 w-6 text-gray-500 animate-spin" />
    ) : (
    <Delete className="h-6 w-6 text-red-500" />
    )}

    Our deleteItemMutation takes the id of the item and we handle the response by filtering out the item from the list that we get from running a feeds/bundles query. We can also run an easy optimisticResponse by simply returning the deleted item's id and __typename.

    Now create some feeds and bundles and watch that when you delete them, they delete almost instantaneously- sweet!