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.
21. Create Items and Item Detail pages
Objective: Create feeds, feed, bundles, and bundle pages.
We have been using the home page to make sure our <ItemList>
component is working, so now let's add this to the bundles and bundle detail page. After, we will do the same for the feeds and feed detail page. First create a bundles.tsx
page
pages/bundles.tsx
tsx
import { useState } from 'react'import { Layout } from '../components/layout'import { ItemType, SelectedFeedState } from '../utils/types'import { ItemList } from '../components/itemList'const BundlesPage = () => {const initialSelected: SelectedFeedState = {id: null,feeds: [],editMode: false,newMode: false,}const [selected, setSelected] = useState(initialSelected)return (<Layout><div className="grid grid-cols-2"><h3 className="grid-cols-1 justify-start flex text-lg font-medium py-4">Bundles Page</h3></div><ItemListtype={ItemType.BundleType}useSelected={true}allowEdits={true}selected={selected}setSelected={setSelected}/></Layout>)}export default BundlesPage
This is almost a duplicate to the home page, except we have tweaked the allowEdits prop in the ItemList to be true- it doesn't matter now but it will later when we add that capability.
pages/bundle/[id].tsx
tsx
import { useQuery } from '@apollo/client'import { NotifyError } from '../../components/notifyError'import { Layout } from '../../components/layout'import { OneListItem } from '../../components/oneListItem'import { BUNDLE_QUERY } from '../../utils/api/graphql/queries'import { FeedObject, ItemType } from '../../utils/types'import { NotifyLoading } from '../../components/notifyLoading'const Bundle = ({ id }) => {const { loading, error, data } = useQuery(BUNDLE_QUERY, {variables: { data: { id: id } },})if (loading) {return (<Layout><NotifyLoading /></Layout>)}const { bundle } = data || {}if (error || !bundle) {return (<Layout><NotifyError /></Layout>)}return (<Layout><h3 className="text-lg font-medium pt-4">{bundle.name}</h3><p className="pb-4">{bundle.description}</p><h3 className="pb-4 font-medium">Feeds</h3><div className="grid grid-cols-3 gap-4">{bundle.feeds.length > 0 ? (bundle.feeds.map((item: FeedObject) => (<OneListItem item={item} type={ItemType.FeedType} key={item.id} />))) : (<p>None are present. Why not add one?</p>)}</div></Layout>)}Bundle.getInitialProps = ({ query }) => {const { id } = queryreturn { id }}export default Bundle
Here we call the bundles query by taking the id prop from the url that is entered. Next.js has this nice convention where if you create a folder and then add a file named [id].tsx
, it will be called when someone goes to the route /foldername/some-id-here
. The some-id-here
in this example would be saved as the paramter id
which we can have access to by using getInitialProps
and pulling it off the query
parameter. We will take this id
and return it from the getInitialProps
function which will then inject it as a real prop into the component. We take that prop and run a bundles query with it to fetch the data.
We handle the loading or error case nicely with a loading or error component surrounded by the layout component so we won't lose the navbar when those two events occur. Once loaded, we will display the different attributes like name, description, and feeds.
Let's now apply the same principals to the feeds and feed detail page. First the feeds page:
pages/feeds.tsx
tsx
import { ItemList } from '../components/itemList'import { NewEditItem } from '../components/newEditItem'import { Layout } from '../components/layout'import { ItemType, SelectedFeedState } from '../utils/types'import { useState } from 'react'const FeedsPage = () => {const initialSelected: SelectedFeedState = {id: null,feeds: [],editMode: false,newMode: false,}const [selected, setSelected] = useState(initialSelected)return (<Layout><div className="grid grid-cols-2"><h3 className="grid-cols-1 justify-start flex text-lg font-medium py-4">Feeds Page</h3></div><ItemListtype={ItemType.FeedType}useSelected={true}allowEdits={true}selected={selected}setSelected={setSelected}/></Layout>)}export default FeedsPage
This is the first time we are calling the <ItemList>
component with the type FeedType
as opposed to BundleType
. Fortunately, since the <ItemList>
component is flexible enough to know what kind of query to run, all that is needed is to switch the type and we are all set.
pages/feed/[id].tsx
tsx
import { useQuery } from '@apollo/client'import { Layout } from '../../components/layout'import { NotifyError } from '../../components/notifyError'import { NotifyLoading } from '../../components/notifyLoading'import { OneListItem } from '../../components/oneListItem'import { FEED_QUERY } from '../../utils/api/graphql/queries'import { BundleObject, ItemType } from '../../utils/types'const Feed = ({ id }) => {const { loading, error, data } = useQuery(FEED_QUERY, {variables: { data: { id: id } },})if (loading) {return (<Layout><NotifyLoading /></Layout>)}const { feed } = data || {}if (error || !feed) {return (<Layout><NotifyError /></Layout>)}return (<Layout><h3 className="text-lg font-medium pt-4">{feed.name}</h3><p className="pb-4">{feed.url}</p><h3 className="pb-4 font-medium">Bundles</h3><div className="grid grid-cols-3 gap-4">{feed.bundles.length > 0 ? (feed.bundles.map((item: BundleObject) => (<OneListItem item={item} type={ItemType.BundleType} key={item.id} />))) : (<p>This feed does not belong to any bundles.</p>)}</div></Layout>)}Feed.getInitialProps = ({ query }) => {const { id } = queryreturn { id }}export default Feed
The same story is with the feed detail page- it is almost a carbon copy of the bundle page that we already created.
Test that all 4 pages work by clicking on the feeds and bundles navbar item at the top and then clicking down into individual feeds or bundles by clicking on the cards themselves.