React Router v6 – access nested routes and correct way to handle an url written by hand

I’m building a bibliothek service using mainly react, redux-toolkit and react-router.

I would like to add a dynamic route, which is a page that displays the details of the clicked book. This route should be reachable from the Home page, where I have some cover book images, and the Catalogue page, where the entire list is showed. I’m quite confused about this.

  1. The most important. There is something that I miss. I tried the following, but doesn’t work. I tried to navigate to the /catalogue/:bookId from the Home page and the Catalogue page, but it doesn’t render the correct component. I think I misunderstood the sense of the children inside the route object, any suggestion? Should I use the <Outlet /> and which is the correct flow for this?

Part of the router.tsx

      {
        path: '/',
        element: <Home />,
      },
      {
        path: 'catalogue',
        element: <Catalogue />,
        children: [
          {
            path: '/catalogue/:bookId',
            element: <BookDetails />
          }
        ]
      },

Part of the <Catalogue /> component in which I render a for each book in the list.

       {
          list.map((book, i) => {
            return (
              <BookCard
                book={book}
                key={`book-${i}`} />
            )
          })
        }

<BookCard /> component

const BookCard = ({ book }: {
  book: IBook;
}) => {
  const dispatch = useAppDispatch();
  const navigate = useNavigate();

  const handleClick = () => {
    dispatch(exploredBook(book));
    navigate(`/catalogue/${book.id}`)
  }

  return (
    <Col>
      <Card className="book-card">
        <Row>
          <Col>
            <Card.Img 
              className="book-img"
              variant="top"
              src={book.imageLink}
              alt={`${book.title} book cover`} />            
          </Col>
          <Col>
            <Card.Body>
              <Card.Title as={Link} to={`/catalogue/${book.id}`}>{book.title}</Card.Title>
              <Card.Subtitle>{book.author}, {book.year}</Card.Subtitle>
              {  
                book.book_status.copies <= 0
                  ? <Badge bg="danger">Out of stock</Badge>
                  : <Badge bg="success">{book.book_status.copies} copies available</Badge>
              }
              <CatalogueActions book={book} />
              <Card.Link href={book.link} target='_blank'>
                <small>More about <em>{book.title}</em> on Wikipedia</small>
              </Card.Link>
            </Card.Body>
          </Col>
        </Row>
      </Card>
    </Col>
  )
};

export default BookCard;
  1. My second question is: when the user arrives on the details of the book, the url should be something like /localhost:3000/catalogue/o23ifo2eifj8249g2g24g. But if the user, write by hand the url and inserts a random id, the page will render nevertheless the content. How can I prevent the rendering of the content? My simple solution would be, make a search in the database using the hook useParams() for the written id: if exist, the corresponding book is rendered, if doesn’t exist, a 404 page is rendered. Is there a better simplier solution?

>Solution :

With the current routes config:

{
  path: '/',
  element: <Home />,
},
{
  path: 'catalogue',
  element: <Catalogue />,
  children: [
    {
      path: '/catalogue/:bookId', // <-- or simply ":bookId"
      element: <BookDetails />
    }
  ]
},

A nested route on path="/catalogue/:bookId" is rendered.

Should I use the <Outlet /> and which is the correct flow for this?

Yes, in this configuration the Catalogue component necessarily should render an Outlet component for nested routes to render their content into. This is the use case where you want to render the Catalogue and BookDetails simultaneously.

Catalogue Example:

{list.map((book, i) => {
  return (
    <BookCard
      book={book}
      key={`book-${i}`} />
    )
  })
}
...

<Outlet />

If this isn’t the case and you want Catalogue and BookDetails to render each on their own independent pages then the routes should be unnested.

Example:

{
  path: '/',
  element: <Home />,
},
{
  path: 'catalogue',
  element: <Catalogue />,
},
{
  path: '/catalogue/:bookId',
  element: <BookDetails />
}

or

{
  path: '/',
  element: <Home />,
},
{
  path: 'catalogue',
  children: [
    {
      index: true,
      element: <Catalogue />
    },
    {
      path: ':bookId',
      element: <BookDetails />
    }
  ]
},

When the user arrives on the details of the
book, the url should be something like
"/catalogue/o23ifo2eifj8249g2g24g". Iif the user,
write by hand the url and inserts a random id, the page will render
nevertheless the content. How can I prevent the rendering of the
content? My simple solution would be, make a search in the database
using the hook useParams() for the written id: if exist, the
corresponding book is rendered, if doesn’t exist, a 404 page is
rendered. Is there a better simpler solution?

This is a standard practice to validate dynamic route path parameters and either redirect a user to a 404 page or to conditionally render some fallback UI by the BookDetails page when consuming an invalid bookId value.

Leave a Reply