skip to Main Content

I am using React and react-router. When I try to go to this route <Route path="/blogs/:id" element={<Blog/>} /> from the Blogs page the whole content of the Blog page content is blank and I have a lot of errors.

enter image description here
enter image description here

App.jsx

import Footer from "./components/Footer"
import Navbar from "./components/Navbar"
import Blogs from "./pages/Blogs"
import Cart from "./pages/Cart"
import Home from "./pages/Home"
import Shop from "./pages/Shop"
import { Routes, Route } from 'react-router-dom'
import './App.css'
import Blog from "./pages/Blog"

const App = () => {
  return (
    <>
      <Navbar />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/shop" element={<Shop />} />
        <Route path="/cart" element={<Cart />} />
        <Route path="/blogs" element={<Blogs />} />
        <Route path="/blogs/:id" element={<Blog/>} />
      </Routes>
      <Footer />
    </>
  )
}

export default App

Blogs.jsx

import { Link } from "react-router-dom"
import PageTitle from "../components/PageTitle"
import Data from '../data/Data.json'

const Blogs = () => {
  return (
    <section>
      <PageTitle title={'Blogs'} />

      <div className="container my-5">
        <div className="row">
          {Data.blogs.map((blog, i) => {
            const { id, title, author, image, publishedDate, views } = blog
            const truncatedTitle = title.length > 60 ? title.substring(0, 60) + '...' : title;
            return (
              <Link to={`/blogs/${id}`} key={i} className="col-12 col-sm-6 col-md-4 my-3" style={{ textDecoration: 'none' }}>
                <div>
                  <div className="post-entry">
                    <div className="post-thumbnail">
                      <img src={image} alt="Image" className="img-fluid" />
                    </div>
                    <div className="post-content-entry">
                      <h6 className="my-2">{truncatedTitle}</h6>
                      <div className="meta">
                        <div>by {author}</div>
                        <div>on {publishedDate}</div>
                        <div><i className="fa-solid fa-eye ms-5"></i> {views}</div>
                      </div>
                    </div>
                  </div>
                </div>
              </Link>
            )
          })}
        </div>
      </div>
    </section>
  )
}

export default Blogs

Blog.jsx

import { useEffect, useState } from "react"
import { useParams } from "react-router-dom"
import Data from '../data/Data.json'

const Blog = () => {
  const { id } = useParams()
  const [blog, setBlog] = useState(null);
  const { title, author, image, content, publishedDate, views } = blog
  
  useEffect(() => {
    const singleBlog = Data.blogs.find(item => item.id === parseInt(id))

    if (singleBlog) {
      setBlog(singleBlog)
    }
  }, []);
  
  return (
    <section>
      <div className="container pt-5">
        {blog ? (
          <div className="blog">
            <img src={image} height={'300px'}/>
            <h2>{title}</h2>
            <div>
              <span>by {author}</span>
              <span className="mx-5">on {publishedDate}</span>
              <span><i className="fa-solid fa-eye ms-5"></i> {views}</span>
            </div>
            <p>{content}</p>
          </div>
        ) : 'not found blog'}
      </div>
    </section>
  )
}

export default Blog

2

Answers


  1. The problem is that your blog state is initially null, not an object so title and the other properties can not be destructed from the blog state.

    You can try to resolve it this way:

    import { useEffect, useState } from "react"
    import { useParams } from "react-router-dom"
    import Data from '../data/Data.json'
    const Blog = () => {
      const {id} = useParams()
      const [blog, setBlog] = useState(null);
            
      useEffect(() => {
        const singleBlog = Data.blogs.find(item => item.id === parseInt(id))
    
        if(singleBlog){
          setBlog(singleBlog)
        }
      }, []);
      
      if (blog === null) return <h1>Loading... Please Wait (add custom loading here)</h1>
      if (blog === undefined) return <h1>Blog not found (add custom 404 page here)</h1>
      const { title, author, image, content, publishedDate , views} = blog
      
      return (
        <section>
          <div className="container pt-5">
            {
              blog ? (
                <div className="blog">
                  <img src={image} height={'300px'}/>
                  <h2>{title}</h2>
                  <div>
                    <span>by {author}</span>
                    <span className="mx-5">on {publishedDate}</span>
                    <span><i className="fa-solid fa-eye ms-5"></i> {views}</span>
                  </div>
                  <p>{content}</p>
                </div>
              ) : 'not found blog'
            }
          </div>
        </section>
      )
    }
    
    export default Blog
    

    This way, you won’t try to obtain blog.title until blog is set to an object. There are two cases I added here, I am going to try to walk you through the process here:

      1. User opens a blog, state is equal to null
      1. useEffect runs but that is running in async mode.
      1. UI will render loading component until state switches from null to blog | undefined
      1. A: Blog is found, state is set with the blog, data is obtained and the blog rendered.
      1. B: Blog is NOT found, state is set to undefined, UI renders 404 page.
    Login or Signup to reply.
  2. Issue

    the initial blog state value is null, and immediately after declaring the blog state you attempt to destructure properties from this null value.

    const [blog, setBlog] = useState(null);
    const { title, author, image, content, publishedDate, views } = blog;
    

    You obviously can’t do this.

    Solution

    The UI should wait until blog is a defined object prior to attempting to destructure properties from it. It’s also a bit of a React anti-pattern to store derived "state" into React state. The current blog value is easily computed from the id route path parameter and the imported JSON data.

    Additional things to keep in mind:

    • Array.prototype.find returns undefined if no matching element is found.
    • Route path params are always strings, so it’s likely safer to use a type-safe string comparison than it is to convert the params to number types.

    Example solution:

    import { useParams } from "react-router-dom"
    import data from "../data/Data.json";
    
    const Blog = () => {
      const { id } = useParams();
    
      const blog = data.blogs.find(item => String(item.id) === id);
    
      if (!blog) {
        return <div>not found blog</div>;
      }
    
      // Safe to access blog properties now
      const { title, author, image, content, publishedDate, views } = blog;
      
      return (
        <section>
          <div className="container pt-5">
            <div className="blog">
              <img src={image} height="300px" />
              <h2>{title}</h2>
              <div>
                <span>by {author}</span>
                <span className="mx-5">on {publishedDate}</span>
                <span>
                  <i className="fa-solid fa-eye ms-5" /> {views}
                </span>
              </div>
              <p>{content}</p>
            </div>
          </div>
        </section>
      );
    };
    
    export default Blog;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search