Archived student lesson

Add ItemList component

Part of End to End React with Prisma 2

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

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.FeedType
  const { 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

export enum ItemType {
  BundleType = 'BundleType',
  FeedType = 'FeedType',
}

Now, let’s import the <ItemList> component on the index page.

pages/index.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

// const { user: auth0User } = await auth0.getSession(req);
// Comment this out after the frontend is built
const 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

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

export const ErrorSign = ({ 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="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

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

// other svg above

export const WaitingClock = ({ 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="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

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.FeedType
  const { loading, error, data } = useQuery(
    isFeed ? FEEDS_QUERY : BUNDLES_QUERY
  )
  const { feeds, bundles } = data || {}
  const itemList = isFeed ? feeds : bundles

  if (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

// other types above

export type FeedObject = {
  id: string
  name: string
  url: string
  tags: TagObject[]
  bundles: BundleObject[]
  author: AuthorObject
  likes: AuthorObject[]
}

export type BundleObject = {
  id: string
  name: string
  description: string
  tags: TagObject[]
  feeds: FeedObject[]
  author: AuthorObject
  likes: AuthorObject[]
}

export type AuthorObject = {
  id: string
  auth0: string
  picture: string
  nickname: string
}

export type TagObject = {
  name: string
  id: 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!