Search code examples
reactjsreact-routerpage-refresh

Reactjs - Accessing Post directly or hard refresh lead to blank page


i have been tiring to solve this problem for two days but no luck This is my first project with react v18.1, basically I'm stuck with single post, Going from blog list to single post Loads fine, but if I refresh (Browser refresh button) single post or try to access to it directly I get blank page

It's Like the data won't load unless It passes through Blog List.

This is my Route from index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter, Routes, Route } from "react-router-dom";
import './index.css';
import './global.css';
import App from './App';
import About from './components/About';
import Contact from './components/Contact';
import NotFound from './components/NotFound';
import Blog from './components/Blog';
import Doctors from './components/Doctors';
import registerServiceWorker from './registerServiceWorker';
import Singlepost from './components/singlepost';
import Home from './components/Home';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/" element={<App />} >
        <Route path="Doctors" element={<Doctors />} />
        <Route path="Blog" element={<Blog />} />
        <Route path="blog/:articleId" element={<Singlepost />} />
        <Route path="About" element={<About />} />
        <Route path="Contact" element={<Contact />} />
        <Route path="*" element={<NotFound />} />
      </Route>

    </Routes>
  </BrowserRouter>
);
registerServiceWorker();

Code from App.js

import { useState, useEffect } from "react";
import Header from "./components/header/header";
import Footer from "./components/footer";
import { Outlet } from "react-router-dom";

function App() {
  // storing the current page number and data in state
  const [blogData, setBlogData] = useState([]);
  const [url, setUrl] = useState(`${process.env.PUBLIC_URL}/blogpost.json`);

  // Fetching data from json file
  const dataFetch = async () => {
    const response = await fetch(url, {
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }

    });
    const data = await response.json();

    setBlogData(data);
  }

  useEffect(() => {

    dataFetch()

  }, []);


  return (
    <>

      <Header />
      <div className="flex flex-col my-20">
        <Outlet context={[blogData]} />
      </div>
      <Footer />

    </>
  );
}

export default App;

Code from blog.js page

import React, { useState, useMemo, useEffect } from 'react'
import { Link, Outlet, useOutletContext, } from 'react-router-dom';
import Pagination from './Pagination';

let PageSize = 10;
const MAX_LENGTH = 500;

export default function Blog() {

  const [articles] = useOutletContext();
  const [currentPage, setCurrentPage] = useState(1);


  // pagination relation function
  let currentTableData = useMemo(() => {
    const firstPageIndex = (currentPage - 1) * PageSize;
    const lastPageIndex = firstPageIndex + PageSize;
    return articles.slice(firstPageIndex, lastPageIndex);
  }, [currentPage, articles]);

  function slugify(text) {
    return text.toString().toLowerCase()
      .replace(/\s+/g, '-')           // Replace spaces with -
      .replace(/[^\w\-]+/g, '')       // Remove all non-word chars
      .replace(/\-\-+/g, '-')         // Replace multiple - with single -
      .replace(/^-+/, '')             // Trim - from start of text
      .replace(/-+$/, '');            // Trim - from end of text
  }

  return (
    <>

      {
        currentTableData.map(doc => (
          <div key={crypto.randomUUID()} className="flex flex-col md:flex-row w-full m-auto mb-4 shadow rounded border">
            <div>
              <img src={doc.Image_URL} alt="doctor" className="w-full h-64 md:h-auto md:w-64 rounded-l md:mr-4 max-h-[200px]" />

            </div>

            <div className='flex flex-col'>
              <Link to={`/blog/${doc.ID}`} className='font-semibold'>{doc.Title}</Link>
              <div className='flex flex-col'>

                {doc.Content.length > MAX_LENGTH ?
                  (
                    <div className='prose lg:prose-xl' dangerouslySetInnerHTML={{ __html: doc.Content.substring(0, MAX_LENGTH) }}>

                    </div>
                  ) :
                  <p className='prose lg:prose-xl' dangerouslySetInnerHTML={{ __html: doc.Content }} ></p>
                }

                <span>{doc.Category}</span>
              </div>
            </div>
          </div>

        ))
      }
      <Outlet context={[articles]} />
      <Pagination
        key={crypto.randomUUID()}
        data-link={crypto.randomUUID()}
        className="pagination-bar"
        currentPage={currentPage}
        totalCount={articles.length}
        pageSize={PageSize}
        onPageChange={page => setCurrentPage(page)}
      />


    </>
  )
}

SinglePost.js

import React from 'react'
import { useOutletContext, useParams } from 'react-router-dom';

export default function Singlepost() {

  let params = useParams();

  const [blogData] = useOutletContext();


  const blogPost = blogData.find(blog => blog.ID === parseInt(params.articleId));


  return (
    <div className="flex flex-col md:flex-row w-full m-auto mb-4 shadow rounded border">
      <div>
        <img src={blogPost.Image_URL} alt="doctor" className="w-full h-64 md:h-auto md:w-64 rounded-l md:mr-4 max-h-[200px]" />

      </div>
      <div className='flex flex-col'>
        <h2 className='font-semibold'>{blogPost.Title}</h2>
        <div className='flex flex-col'>
          <div className='prose lg:prose-xl' dangerouslySetInnerHTML={{ __html: blogPost.Content }}></div>
          <span>{blogPost.Category}</span>
        </div>
      </div>

    </div>
  )
}

Error after hard refresh

singlepost.js:17 Uncaught TypeError: Cannot read properties of undefined (reading 'Image_URL')
    at Singlepost (singlepost.js:17:1)
    at renderWithHooks (react-dom.development.js:16175:1)
    at mountIndeterminateComponent (react-dom.development.js:20913:1)
    at beginWork (react-dom.development.js:22416:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:4161:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4210:1)
    at invokeGuardedCallback (react-dom.development.js:4274:1)
    at beginWork$1 (react-dom.development.js:27405:1)
    at performUnitOfWork (react-dom.development.js:26513:1)
    at workLoopSync (react-dom.development.js:26422:1
    --------------------------------------------------------------
    singlepost.js:17 Uncaught TypeError: Cannot read properties of undefined (reading 'Image_URL')
    at Singlepost (singlepost.js:17:1)
    at renderWithHooks (react-dom.development.js:16175:1)
    at mountIndeterminateComponent (react-dom.development.js:20913:1)
    at beginWork (react-dom.development.js:22416:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:4161:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4210:1)
    at invokeGuardedCallback (react-dom.development.js:4274:1)
    at beginWork$1 (react-dom.development.js:27405:1)
    at performUnitOfWork (react-dom.development.js:26513:1)
    at workLoopSync (react-dom.development.js:26422:1)
    -------------------------------------------------------------------
react-dom.development.js:18572 The above error occurred in the <Singlepost> component:

    at Singlepost (http://localhost:3000/static/js/bundle.js:1857:75)
    at Outlet (http://localhost:3000/static/js/bundle.js:68128:26)
    at div
    at App (http://localhost:3000/static/js/bundle.js:38:82)
    at Routes (http://localhost:3000/static/js/bundle.js:68220:5)
    at Router (http://localhost:3000/static/js/bundle.js:68153:15)
    at BrowserRouter (http://localhost:3000/static/js/bundle.js:66962:5)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
---------------------------------------------------------------------------
Uncaught TypeError: Cannot read properties of undefined (reading 'Image_URL')
    at Singlepost (singlepost.js:17:1)
    at renderWithHooks (react-dom.development.js:16175:1)
    at mountIndeterminateComponent (react-dom.development.js:20913:1)
    at beginWork (react-dom.development.js:22416:1)
    at beginWork$1 (react-dom.development.js:27381:1)
    at performUnitOfWork (react-dom.development.js:26513:1)
    at workLoopSync (react-dom.development.js:26422:1)
    at renderRootSync (react-dom.development.js:26390:1)
    at recoverFromConcurrentError (react-dom.development.js:25806:1)
    at performConcurrentWorkOnRoot (react-dom.development.js:25706:1)

I appreciates any thoughts. Thank you.


Solution

  • Issue

    You are forgetting that array.prototype.find returns undefined when no match is found in the array.

    const blogPost = blogData.find(blog => blog.ID === parseInt(params.articleId));
    

    In this case it's because the blogData array is empty and there is no match to be found.

    This alone isn't what causes the problem, it's only the first step in leading to thrown errors. The error is thrown when later trying to render the found result and attempting to access into an undefined value.

    <img
      src={blogPost.Image_URL} // <-- can't access Image_URL of undefined!
      alt="doctor"
      className="......"
    />
    

    Solution

    There are a few things you can do to improve the code.

    1. Check that there is a renderable blogPost object. Conditionally render the single post UI.

      export default function Singlepost() {
        const { articleId } = useParams();
        const [blogData] = useOutletContext();
      
        const blogPost = blogData.find(blog => String(blog.ID) === articleId);
      
        if (!blogPost) return null;
      
        return (
          ... blogPost ...
        );
      }
      
    2. Expose the dataFetch in the context so blog posts can be fetched from children if necessary. They can render some loading indicator if desired.

      App

      function App() {
        // storing the current page number and data in state
        const [blogData, setBlogData] = useState([]);
        const [url, setUrl] = useState(`${process.env.PUBLIC_URL}/blogpost.json`);
      
        // Fetching data from json file
        const dataFetch = async () => {
          const response = await fetch(url, {
            headers: {
              'Content-Type': 'application/json',
              'Accept': 'application/json'
            }
          });
          const data = await response.json();
      
          setBlogData(data);
        }
      
        useEffect(() => {
          dataFetch();
        }, []);
      
        return (
          <>
            <Header />
            <div className="flex flex-col my-20">
              <Outlet context={{ blogData, dataFetch }} />
            </div>
            <Footer />
          </>
        );
      }
      

      SinglePost

      export default function Singlepost() {
        const { articleId } = useParams();
        const { blogData, dataFetch } = useOutletContext();
      
        const blogPost = blogData.find(blog => String(blog.ID) === articleId);
      
        useEffect(() => {
          if (!blogPost) {
            dataFetch();
          }
        }, [blogPost, dataFetch]);
      
        if (!blogPost) return null; // or loading spinner/etc...
      
        return (
          ... blogPost ...
        );
      }