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.
18. Add ItemList component
Objective: Create a component which will allow us to list our saved articles.
Now we will build our item list which we will use for displaying a list of either bundles
or feeds
. First create the itemList
component in your components folder.
components/itemList.tsx
tsx
import { useQuery } from '@apollo/client'import { BUNDLES_QUERY, FEEDS_QUERY } from '../utils/api/graphql/queries'import { ItemType } from '../utils/types'export const ItemList = ({ type }: { type: ItemType }) => {const isFeed = type === ItemType.FeedTypeconst { loading, error, data } = useQuery(isFeed ? FEEDS_QUERY : BUNDLES_QUERY)console.log(loading, error, data)return <></>}
We are starting off by just calling the useQuery
hook with either a feeds
or bundles
query depending on what type we send into the component. Let's define the ItemType
now:
utils/types.ts
tsx
export enum ItemType {BundleType = 'BundleType',FeedType = 'FeedType',}
Now, let's import the <ItemList>
component on the index page.
pages/index.tsx
tsx
import { ItemList } from '../components/itemList'import { Layout } from '../components/layout'import { ItemType } from '../utils/types'const Index = () => {return (<Layout><h3 className="justify-start flex text-lg font-medium py-4">Home Page</h3><ItemList type={ItemType.BundleType} /></Layout>)}export default Index
When we go to our webpage and look at the console logs, we'll see an output for loading, error, and data. Hopefully we'll have 1 bundle left over from when we created it back in the backend section, but if you cleared it away for any reason just go ahead and make one or a few bundles by going to http://localhost:3000/api/graphl
. One gotcha is that you will run into problems with the create mutation because we disabled the auto-user adding in the context
component, so we can temporarily revert that change to make it work. Revert this change to make it possible to add items from the api:
utils/api/context.ts
ts
// const { user: auth0User } = await auth0.getSession(req);// Comment this out after the frontend is builtconst auth0User = { nickname: 'faker', sub: '1', picture: '/blank.png' }
In any case, once we are actually loading the <ItemList>
component we can start to build it out. The first thing to add is handling for error and loading states.
Let's create the error first. Create the following component- it is simply a svg component inside of a div.
components/notifyError.tsx
tsx
import { ErrorSign } from './svg'export const NotifyError = () => (<div className="h-screen flex"><ErrorSign className="h-10 w-10 text-gray-500 m-auto" /></div>)
In order to help organize our code, all of the svgs are located in a single file called svg. Create a new file called svg.tsx.
components/svg.tsx
tsx
export const ErrorSign = ({ 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="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>)
All of these svgs are being pulled off of the HeroIcons website, which is an official source for Next.js approved icons. For all the icons that we will use in this app, I simply went on there, copied the svg and then created a functional component from it.
In order to make the svg customizable, I added a prop called className
which will allow me to style the same icon different ways on different pages. In this case, we are setting the height and width to 10 and making it gray. All of these classnames that I'm applying are taken by tailwind and the styles are applied.
Create the loading component now.
components/notifyLoading.tsx
tsx
import { WaitingClock } from './svg'export const NotifyLoading = () => (<div className="h-screen flex"><WaitingClock className="h-10 w-10 text-gray-500 m-auto" /></div>)
components/svg.tsx
tsx
// other svg aboveexport const WaitingClock = ({ 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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>)
Now let's update the <ItemList>
component. We will run the appropriate query and then create an variable called itemList
which will either be feeds or bundles depending on what the type
prop that we passed in was.
components/itemList.tsx
tsx
import { useQuery } from '@apollo/client'import { BUNDLES_QUERY, FEEDS_QUERY } from '../utils/api/graphql/queries'import { BundleObject, FeedObject, ItemType } from '../utils/types'import { NotifyError } from './notifyError'import { NotifyLoading } from './notifyLoading'export const ItemList = ({ type }: { type: ItemType }) => {const isFeed = type === ItemType.FeedTypeconst { loading, error, data } = useQuery(isFeed ? FEEDS_QUERY : BUNDLES_QUERY)const { feeds, bundles } = data || {}const itemList = isFeed ? feeds : bundlesif (loading) {return <NotifyLoading />}if (error || !itemList) {return <NotifyError />}return (<><div className="grid lg:grid-cols-3 md:grid-cols-2 gap-4">{itemList.length > 0 ? (itemList.map((item: FeedObject | BundleObject) => (<p key={item.id}>{item.id}</p>))) : (<p>None are present. Why not add one?</p>)}</div></>)}
If the query loads, we then will try and display it in a list. We will eventually create a component to load for each item on the list, but for now we are just displaying a paragraph tag with the id.
There are two types FeedObject
and BundleObject
which we have not defined, so let's add those now.
utils/types.ts
ts
// other types aboveexport type FeedObject = {id: stringname: stringurl: stringtags: TagObject[]bundles: BundleObject[]author: AuthorObjectlikes: AuthorObject[]}export type BundleObject = {id: stringname: stringdescription: stringtags: TagObject[]feeds: FeedObject[]author: AuthorObjectlikes: AuthorObject[]}export type AuthorObject = {id: stringauth0: stringpicture: stringnickname: string}export type TagObject = {name: stringid: string}
Since feeds and bundles also have author and tag types, let's add those in too.
We should now see that the page renders text for each bundle that you have. In the next step, we will build the <OneListItem>
component which will display each of these items as a card. Let's get to it!