Gregory Wiley
Gregory Wiley

Reputation: 73

Fetching Data from API using NextJS and Material UI React

I am trying to create dynamic pages that shows individual book details (i.e. title/author etc) on a separate page based on a query string of the "id" of each book. However, I am having difficulty in understanding how to make a request to a API endpoint using NextJS that will get the book details based on its "id". I would like to use Material UI as a UI Framework.

ISSUE: When I run npm run dev the book page loads but the book's "props" are not being passed along to the BookAttributes component. The console.log(book) I added in the book page is undefined and the console.log(title) in BookAttributes is undefined as well.

  1. I've tested the API endpoint in POSTMAN and it appears to work.
  2. When I refactor the same code using Semantic UI-React instead of Material UI, the book pages load correctly.
  3. I am using the NextJS Material UI starter template from the Material UI website as a baseline.

I am fairly new to NextJS and Material UI so your assistance and guidance would be greatly appreciated. Thank you for your help on this!

Here is the code I have so. I have tried to keep in clean and simple.

BOOK PAGE (within 'pages' directory)

import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';

function Book({ book }) {

    console.log(book)

    return (
        <>
            <h1>Book Page</h1>
            <BookAttributes {...book} />
        </>
    )
}

Book.getInitalProps = async ({ query: { _id } }) => {
    const url = 'http://localhost:3000/api/book';
    const payload = { params: { _id }}
    const response = await axios.get(url, payload)
    return { book: response.data }
}

export default Book;

BOOK API ENDPOINT (within 'pages/api' directory)

import Book from '../../models/Book';
import connectDb from '../../utils/connectDb';

connectDb()

export default async (req, res) => {
    const { _id } = req.query
    const book = await Book.findOne({ _id })
    res.status(200).json(book);
}

BOOK ATTRIBUTE COMPONENT (within 'components' directory)

import React from 'react';

function BookAttributes({ title }) {

    console.log(title)

    return (
        <>
            <h1>{title}</h1>
        </>
    )
}

export default BookAttributes;

Upvotes: 2

Views: 3508

Answers (1)

subashMahapatra
subashMahapatra

Reputation: 6847

You should be using dynamic routes here if you want to work with data-fetching methods like getStaticProps or getServerSideProps.

You can create a page like pages/book/[id].js. But to generate the page you have to decide what data-fetching method you want to run. If the data for the page doesn't change very often you can choose to use static-site-generation using getStaticProps which will generate the pages at build time. If the data will be changing a lot you can either do server-side-rendering using getServerSideProps or fetch the data client-side.

Here is an example for your use-case that you can use for server-side-rendering using getServerSideProps, keep in mind the API call inside getServerSideProps might fail so you should have appropriate error handling.

In pages/book/[id].js

import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';

export const getServerSideProps = async (ctx) => {
   const bookId = ctx.params?.id
   const url = 'http://localhost:3000/api/book';
   const response = await axios.get(url, { params: { _id: bookId} })

   return {
      props: {
        book: response.data
      }
   }

}

function Book({ book }) {
   return (
      <>
        <h1>Book Page</h1>
        <BookAttributes {...book} />
     </>
    )
}

export default Book;

Using static-site-generation Because the page is dynamic you have to provide a list of paths for which nextjs will generate the pages. You can do that by exporting an async function called getStaticPaths.

in pages/book/[id].js

import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';

export const getStaticPaths = async () => {
  // here you have two options if you know all the ids of the books
  // you can fetch that data from the api and use all the ids to generate
  // a list of paths or show a fallback version of page if you don't know all
  // ids and still want the page to be static
  // Pseudo code might look like this
  const res = await axios.get('api-endpoint-to-fetch-all-the-books')

  const paths = res.data.map(book => ({ params: { id: book.id }}))

  return {
     paths,
     fallback: false
  }
}

export const getStaticProps = async (ctx) => {
   const bookId = ctx.params?.id
   const url = 'http://localhost:3000/api/book';
   const response = await axios.get(url, { params: { _id: bookId} })

   return {
      props: {
        book: response.data
      }
   }

}

function Book({ book }) {
   return (
      <>
        <h1>Book Page</h1>
        <BookAttributes {...book} />
     </>
    )
}

export default Book;

The fallback property in the returned value of getStaticPaths is somewhat important to understand. If you know all the necessary id for the pages you can set the fallback to false. In this case nextjs will simply show a 404 error page for all the paths that were not returned from the function getStaticPaths.

If fallback is set to true nextjs will show a fallback version of page instead of a 404 page for the paths that were not returned from the getStaticPaths function. Now where should you set fallback to true? Let's suppose in your case new books are added to the database frequently, but the data for the books doesn't change very often so you want the pages to be static. In this case, you can set fallback to true and generate a list of paths based on avaliable book ids. For the new books nextjs will first show the fallback version of the page than fetch the data based on the id provided in the request and will send the data as JSON which will be used to render the page in the client.

Upvotes: 0

Related Questions