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 | BundleObjecttype: 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 }) => (<svgxmlns="http://www.w3.org/2000/svg"fill="none"viewBox="0 0 24 24"stroke="currentColor"className={className}><pathstrokeLinecap="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 ? (<ItemEdititem={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 | BundleObjecttype: ItemType}) => {const isFeed = type === ItemType.FeedTypeconst __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><divclassName="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"><buttononClick={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"><buttononClick={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}<divonClick={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<buttononClick={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!