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>
    <ItemList
    type={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 } = query
    return { 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>
    <ItemList
    type={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 } = query
    return { 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.