Building an Interactive Blog with React Routing: Unlocking Dynamic Navigation and Seamless User Experience
Welcome to our blog post, where we dive into the world of React Routing and its application in creating a simple yet powerful blog site. In today's digital landscape, websites are no longer static pages; they are dynamic, interactive experiences that engage users and provide seamless navigation. With React Routing, we can harness the power of React.js to build a robust blog site with smooth transitions, dynamic content loading, and efficient URL management.
Gone are the days of manually reloading pages or struggling with clunky navigation menus. React Routing empowers developers to create single-page applications that offer a fluid user experience, mimicking the responsiveness of native applications. By leveraging the power of routing, we can build a blog site that seamlessly transitions between different sections, such as homepage, blog posts, about, and more, without the need for full page reloads.
In this blog post, we will take you on a step-by-step journey of building a simple blog site using React Routing. We'll explore the fundamental concepts of routing, set up our project environment, and delve into the implementation details of creating different routes for various sections of our blog.
You don't need to be a React expert to follow along; we'll explain the concepts and provide code examples to guide you through the process. By the end of this tutorial, you'll have a solid understanding of how React Routing works and how to leverage its features to create an engaging and user-friendly blog site.
So, if you're ready to enhance your web development skills and take your blog site to the next level, let's jump right in and explore the wonders of React Routing!
Tutorial
In this tutorial we will understand how react routing works by building a simple multi-page blog website.
Initial Setup & Installation
First, begin by creating a new react app using the following command in your terminal:
npx create-react-app blog-site-react --save
This will give us a new folder with a boilerplate code for a sample react project. Change the folder directory to the newly created app using the command cd blog-site-react
and type code .
to open the project in a new VS code window.
Since we are not concerned with testing at the moment and we will introduce our own styling, just remove all the unnecessary files from the src
folder and keep the following files in it: App.js
, index.css
, and index.js
.
Setting Up Routes
To begin with routing, we will install the routing package from the node package manager using the following command:
npm install react-router-dom --save
Note that it will install the latest version of React Router viz. v6 at the time this was written. We can also specify the version of the router in the above command like this: npm install react-router-dom@6 --save
or any other version we would like to work with.
Next, we will make changes to our index.js file in order to make the routing work in the following way:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter as Router, Route } from "react-router-dom";
import { Routes } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Router>
<Routes>
<Route path="/*" element={<App />} />
</Routes>
</Router>
</React.StrictMode>
);
Here's an explanation of the above code:
- Importing Dependencies:
React
,ReactDOM
,./index.css
,App
,BrowserRouter
,Route
,Routes
: These imports serve the same purposes as in the previous code snippets.
- Creating a React Root:
const root = ReactDOM.createRoot(document.getElementById("root"));
: This line creates a React root using thecreateRoot
method provided by ReactDOM. It takes an HTML element with the id "root" as an argument and returns a root object.
- Rendering the Application:
root.render(...)
: This line renders the application inside the React root.<React.StrictMode>
: This wrapper component activates additional checks and warnings for potential issues in the application. It is used during development but has no effect in a production build.<Router>
: This component sets up the routing functionality provided byreact-router-dom
. All the components inside this wrapper will have access to routing capabilities.<Routes>
: This component is used as a wrapper for defining multiple routes within the application. It is an alternative syntax to using multiple<Route>
components.<Route path="/*" element={<App />} />
: This line defines a route with a path of "/*" (wildcard path that matches any URL) and associates it with theApp
component. Theelement
prop is used to specify the component to render when the route matches. In this case, it renders theApp
component.</Routes>
: Closes the routes wrapper component.</Router>
: Closes the router component.</React.StrictMode>
: Closes the strict mode wrapper.
In summary, this code sets up a React application with routing using the react-router-dom
library. It renders the App
component for any URL within the application and wraps the application in a strict mode for development purposes. The use of the Routes
component provides an alternative syntax for defining routes.
Create Multiple Pages
Now we will begin by creating the multiple page components for our website. First we will simply create new files in the src
folder by the names of Header.js
, Home.js
, Footer.js
, About.js
, NewPost.js
, PostPage.js
, Missing.js
, and Nav.js
files and then create a boilerplate code in all of them. We can use React snippets extension and type rafce
or can type manually the following code for all of them:
const Home = () => {
return (
<main>
<h1>Home</h1>
</main>
);
};
export default Home;
Simply change the function name to the specific file name and change the semantic HTML tags as per the file for instance in Header.js
, instead of the main
element, we will have <header></header>
and so on.
Once these files are created, import them in App.js
and place them in the JSX as HTML tags with the routes to individual pages.
But first, we will create a Layout.js
file which will hold the common components that need to appear on all pages in the following way:
import Header from "./Header";
import Footer from "./Footer";
import Nav from "./Nav";
import { Outlet } from "react-router-dom";
const Layout = () => {
return (
<div className="App">
<Header />
<Nav />
<Outlet />
<Footer />
</div>
);
};
export default Layout;
The above code exports a React component named Layout
that represents a common layout structure for a web application. Here's an explanation of the code:
- Importing Dependencies:
Header
,Footer
,Nav
: These import statements are used to import other components from separate files. It suggests that there are separate files namedHeader.js
,Footer.js
, andNav.js
that define those components.Outlet
: This import statement imports theOutlet
component from thereact-router-dom
library. TheOutlet
component is used to render the nested child routes within the parent component.
- Defining the Layout Component:
const Layout = () => { ... }
: This defines a functional component namedLayout
using arrow function syntax.- Inside the component's body, it returns JSX code that represents the layout structure of the application.
<div className="App">
: This is adiv
element with the CSS class name "App". It serves as the root container for the layout.<Header />
: This renders theHeader
component. It assumes that theHeader
component is defined in a separate file and exports a React component.<Nav />
: This renders theNav
component. It assumes that theNav
component is defined in a separate file and exports a React component.<Outlet />
: This renders the child routes within the parent component. It indicates that this component serves as a placeholder for rendering child components defined by the routing system.<Footer />
: This renders theFooter
component. It assumes that theFooter
component is defined in a separate file and exports a React component.</div>
: Closes the rootdiv
element.
- Exporting the Layout Component:
export default Layout;
: This exports theLayout
component as the default export of the current module. It means that other parts of the application can import and use this component.
In summary, the Layout
component represents a common layout structure for a web application. It includes a header, navigation, a placeholder for rendering child components defined by the routing system, and a footer. Other components and routes can be nested inside the Layout
component to form the complete application layout.
Create Route Logic
Next we will write the routing logic for the different pages in App.js
file in the following way:
import About from "./About";
import Home from "./Home";
import NewPost from "./NewPost";
import PostPage from "./PostPage";
import Missing from "./Missing";
import { useState, useEffect } from "react";
import { Route, Routes, useNavigate } from "react-router-dom";
import Layout from "./Layout";
function App() {
return (
<Routes>
<Route element={<Layout />} path="/">
<Route index element={<Home />} />
<Route path="post">
<Route index element={<NewPost />} />
<Route path=":id" element={<PostPage />} />
</Route>
<Route path="about" element={<About />} />
<Route path="*" element={<Missing />} />
</Route>
</Routes>
);
}
export default App;
The above code sets up the routing configuration for a React application using the react-router-dom
library. It defines different routes and associates them with specific components. Here's an explanation of the code:
- Importing Dependencies:
About
,Home
,NewPost
,PostPage
,Missing
: These import statements are used to import components from separate files. It suggests that there are separate files namedAbout.js
,Home.js
,NewPost.js
,PostPage.js
, andMissing.js
that define those components.useState
,useEffect
: These import statements import theuseState
anduseEffect
hooks from the React library.Route
,Routes
,useNavigate
: These import statements import components and hooks from thereact-router-dom
library.
- Defining the App Component:
function App() { ... }
: This defines a functional component namedApp
.- Inside the component's body, it returns JSX code that represents the routing configuration for the application.
- Routing Configuration:
<Routes>
: This is the top-level component that wraps all the route definitions.<Route element={<Layout />} path="/">
: This defines a route with a path of "/" (the root path) and associates it with theLayout
component. Theelement
prop is used to specify the component to render when the route matches. In this case, it renders theLayout
component.- Inside the root route, there are several nested
<Route>
components that define child routes and their associated components. <Route index element={<Home />} />
: This defines a route with a path of "/" (the root path) and associates it with theHome
component. It will be rendered when the user navigates to the root URL.<Route path="post">
: This defines a route with a path of "/post" and creates a nested route configuration for posts.<Route index element={<NewPost />} />
: This defines a sub-route with a path of "/post" and associates it with theNewPost
component. It will be rendered when the user navigates to the "/post" URL.<Route path=":id" element={<PostPage />} />
: This defines a dynamic sub-route with a path parameter:id
. It associates thePostPage
component with the route and will be rendered when the user navigates to a URL that matches the pattern "/post/:id".<Route path="about" element={<About />} />
: This defines a route with a path of "/about" and associates it with theAbout
component. It will be rendered when the user navigates to the "/about" URL.<Route path="*" element={<Missing />} />
: This defines a route with a path of "*" (wildcard path) and associates it with theMissing
component. It will be rendered when none of the previous routes match the URL.
- Exporting the App Component:
export default App;
: This exports theApp
component as the default export of the current module. It means that other parts of the application can import and use this component.
In summary, this code sets up the routing configuration for a React application. It defines different routes and associates them with specific components. The Layout
component is used as the top-level layout structure, and various components such as Home
, NewPost
, PostPage
, About
, and Missing
are associated with different routes. The react-router-dom
library is used to handle the routing functionality.
Implement CSS Styling
Now we will use some standard CSS for styling which can be modified as per needs later on in index.css
file in the following way:
@import url("<https://fonts.googleapis.com/css2?family=Open+Sans&display=swap>");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 16px;
}
body {
min-height: 100vh;
font-family: "Open Sans", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: flex;
background-color: #efefef;
}
#root {
flex-grow: 1;
display: flex;
justify-content: center;
align-items: center;
}
.App {
width: 100%;
max-width: 800px;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
border: 1px solid #333;
box-shadow: 0px 0px 15px gray;
}
.Header,
.Footer {
width: 100%;
background-color: #66d8f5;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.Header h1 {
font-size: 1.5rem;
}
.Header svg {
font-size: 2rem;
}
.Footer {
padding: 0.75rem;
display: grid;
place-content: center;
}
.Nav {
width: 100%;
background-color: #333;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
}
.searchForm {
width: 80%;
padding: 1rem 0 0 0.75rem;
}
.searchForm input[type="text"] {
font-family: "Open Sans", sans-serif;
width: 100%;
min-height: 48px;
font-size: 1rem;
padding: 0.25rem;
border-radius: 0.25rem;
outline: none;
}
.searchForm label {
position: absolute;
left: -99999px;
}
.Nav ul {
color: #fff;
list-style-type: none;
display: flex;
flex-wrap: nowrap;
align-items: center;
}
.Nav li {
padding: 1rem;
}
.Nav li:hover,
.Nav li:focus {
padding: 1rem;
}
.Nav li a {
color: #fff;
text-decoration: none;
}
.Nav li:hover,
.Nav li:focus,
.Nav li:hover a,
.Nav li:focus a {
background-color: #eee;
color: #333;
}
.Nav li:hover a,
.Nav li:focus a {
cursor: pointer;
}
.Home,
.NewPost,
.PostPage,
.About,
.Missing {
width: 100%;
flex-grow: 1;
padding: 1rem;
overflow-y: auto;
background-color: #fff;
}
.post {
margin-top: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid lightgray;
}
.Home .post a {
text-decoration: none;
}
.Home .post a,
.Home .post a:visited {
color: #000;
}
.post:first-child {
margin-top: 0;
}
.post:last-child {
border-bottom: none;
}
.postDate {
font-size: 0.75rem;
margin-top: 0.25rem;
}
.postBody {
margin: 1rem 0;
}
.newPostForm {
display: flex;
flex-direction: column;
}
.newPostForm label {
margin-top: 1rem;
}
.newPostForm input[type="text"],
.newPostForm textarea {
font-family: "Open Sans", sans-serif;
width: 100%;
min-height: 48px;
font-size: 1rem;
padding: 0.25rem;
border-radius: 0.25rem;
margin-right: 0.25rem;
outline: none;
}
.newPostForm textarea {
height: 100px;
}
.newPostForm button {
margin-top: 1rem;
height: 48px;
min-width: 48px;
border-radius: 10px;
padding: 0.5rem;
font-size: 1rem;
cursor: pointer;
}
.Missing h2,
.PostPage h2,
.Missing p,
.PostPage p {
margin-bottom: 1rem;
}
.PostPage button {
height: 48px;
min-width: 48px;
border-radius: 0.25rem;
padding: 0.5rem;
font-size: 1rem;
background-color: red;
color: #fff;
cursor: pointer;
}
.statusMsg {
margin-top: 2rem;
}
@media only screen and (min-width: 610px) {
html {
font-size: 22px;
}
.Header h1 {
font-size: 2rem;
}
.Nav {
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
}
.Nav ul {
text-align: right;
}
.Nav li:hover,
.Nav li:focus,
.Nav li:hover a,
.Nav li:focus a {
background-color: #eee;
color: #333;
}
.searchForm {
width: 50%;
padding: 0.5rem 0;
}
.searchForm input[type="text"] {
margin-left: 0.5rem;
}
.newPostForm textarea {
height: 300px;
}
}
@media only screen and (min-width: 992px) {
.Header svg {
font-size: 3rem;
}
}
The above CSS shall be used for styling the entire application and will undergo changes as per requirements.
Create Header Component
Next, we will pass the title property and write the component logic for Header.js
in the following way:
const Header = ({ title }) => {
return (
<header className="Header">
<h1>{title}</h1>
</header>
);
};
export default Header;
The above code defines a React functional component named Header
that represents a header section in a web application. Here's an explanation of the code:
- Function Signature:
const Header = ({ title }) => { ... }
: This defines a functional component namedHeader
using arrow function syntax. It accepts a single argument, which is an object destructuring assignment of thetitle
property.
- Rendering the Header:
- The component's body returns JSX code that represents the structure and content of the header section.
<header className="Header">
: This is an HTML<header>
element with the CSS class name "Header". It serves as the container for the header content.<h1>{title}</h1>
: This is an HTML<h1>
element that displays thetitle
prop value passed to the component. It renders the value dynamically within the header.
- Exporting the Header Component:
export default Header;
: This exports theHeader
component as the default export of the current module. It means that other parts of the application can import and use this component.
In summary, this code defines a React functional component named Header
that represents a header section in a web application. It accepts a title
prop and renders it within an <h1>
element inside a <header>
element. Other components can import and use this Header
component to display a consistent header section with a dynamic title.
Nav Component
After this we will build the Nav component with multiple router links where we will have a search bar and links to different pages in Nav.js
file in the following way:
import { Link } from "react-router-dom";
const Nav = ({ search, setSearch }) => {
return (
<nav className="Nav">
<form className="searchForm" onSubmit={(e) => e.preventDefault()}>
<label htmlFor="search">Search Posts</label>
<input
type="text"
id="search"
placeholder="Search Posts"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</form>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/post">New Post</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
);
};
export default Nav;
The above code defines a React functional component named Nav
that represents a navigation bar in a web application. It uses the Link
component from the react-router-dom
library to provide navigation links. Here's an explanation of the code:
- Importing Dependencies:
Link
: This import statement imports theLink
component from thereact-router-dom
library. TheLink
component is used to create links for navigation within the application.
- Function Signature:
const Nav = ({ search, setSearch }) => { ... }
: This defines a functional component namedNav
using arrow function syntax. It accepts two props:search
andsetSearch
. These props are used to manage search functionality in the navigation component.
- Rendering the Navigation:
- The component's body returns JSX code that represents the structure and content of the navigation bar.
<nav className="Nav">
: This is an HTML<nav>
element with the CSS class name "Nav". It serves as the container for the navigation content.<form className="searchForm" onSubmit={(e) => e.preventDefault()}>
: This is an HTML<form>
element with the CSS class name "searchForm". It handles form submission and prevents the default form behavior to avoid page refresh.<label htmlFor="search">Search Posts</label>
: This is an HTML<label>
element that provides a label for the search input.<input type="text" id="search" placeholder="Search Posts" value={search} onChange={(e) => setSearch(e.target.value)} />
: This is an HTML<input>
element of type "text". It represents the search input field and is associated with thesearch
state value andsetSearch
function through thevalue
andonChange
attributes, respectively.<ul>
: This is an HTML<ul>
element that represents an unordered list of navigation items.<li><Link to="/">Home</Link></li>
: This is an HTML<li>
element that represents a navigation item. The<Link>
component is used to create a link to the root URL ("/") and display the text "Home".<li><Link to="/post">New Post</Link></li>
: This is an HTML<li>
element that represents another navigation item. The<Link>
component is used to create a link to the "/post" URL and display the text "New Post".<li><Link to="/about">About</Link></li>
: This is an HTML<li>
element that represents yet another navigation item. The<Link>
component is used to create a link to the "/about" URL and display the text "About".
- Exporting the Nav Component:
export default Nav;
: This exports theNav
component as the default export of the current module. It means that other parts of the application can import and use this component.
In summary, this code defines a React functional component named Nav
that represents a navigation bar in a web application. It includes a search input field, a list of navigation items with links created using the Link
component, and handles search functionality through the search
and setSearch
props. Other components can import and use this Nav
component to display a navigation bar with search functionality and links for navigation within the application.
Next we will pass the necessary search props to the Nav component in Layout.js
file in the following way:
const Layout = ({ search, setSearch }) => {
return (
<div className="App">
<Header title="React JS Blog" />
<Nav search={search} setSearch={setSearch} />
<Outlet />
<Footer />
</div>
);
};
Next, we will add the search state in the App.js file:
const [search, setSearch] = useState([]);
Create Static Posts Data
After this, we will create a posts state and use some hardcoded post data to display on the Home page in the App.js
file in the following way:
const [posts, setPosts] = useState([
{
id: 1,
title: "React Router",
body: "React Router is a collection of navigational components that compose declaratively with your application.",
datetime: "July 26, 2021 11:17:00 AM",
},
{
id: 2,
title: "React.js",
body: "React is a JavaScript library for building user interfaces. It is maintained by Facebook and a community of individual developers and companies.",
datetime: "July 26, 2021 12:17:00 AM",
},
{
id: 3,
title: "React Hooks",
body: "Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.",
datetime: "July 26, 2021 10:17:00 AM",
},
{
id: 4,
title: "React Context",
body: "Context provides a way to pass data through the component tree without having to pass props down manually at every level.",
datetime: "July 26, 2021 09:17:00 AM",
},
]);
Also, we need to pass the posts state as props to the Home component route like this:
<Route index element={<Home posts={posts} />} />
Create Home Component
Next we will write the logic for the Home page component in Home.js
file where we will import a Feed component that will display a feed of blog posts in the following way:
import Feed from "./Feed";
const Home = ({ posts }) => {
return (
<main className="Home">
{posts.length ? (
<Feed posts={posts} />
) : (
<p style={{ marginTop: "2rem" }}> No posts to display yet! </p>
)}
</main>
);
};
export default Home;
The above code defines a React functional component named Home
that represents the main content area of a home page in a web application. Here's an explanation of the code:
- Importing Dependencies:
Feed
: This import statement is used to import theFeed
component from a separate file. It suggests that there is a file namedFeed.js
that defines theFeed
component.
- Function Signature:
const Home = ({ posts }) => { ... }
: This defines a functional component namedHome
using arrow function syntax. It accepts a single prop namedposts
.
- Rendering the Home Content:
- The component's body returns JSX code that represents the structure and content of the home page.
<main className="Home">
: This is an HTML<main>
element with the CSS class name "Home". It serves as the container for the main content of the home page.{posts.length ? ... : ...}
: This is a conditional rendering statement that checks if theposts
array has any elements. If it does, the first part of the ternary operator (<Feed posts={posts} />
) is rendered. Otherwise, the second part of the ternary operator (<p style={{ marginTop: "2rem" }}> No posts to display yet! </p>
) is rendered.<Feed posts={posts} />
: This renders theFeed
component and passes theposts
array as a prop to it. It suggests that theFeed
component is responsible for rendering the list of posts based on the provided data.<p style={{ marginTop: "2rem" }}> No posts to display yet! </p>
: This is a paragraph element that is rendered when there are no posts to display. It displays the message "No posts to display yet!" and applies some inline CSS to add a margin-top of "2rem" for spacing.
- Exporting the Home Component:
export default Home;
: This exports theHome
component as the default export of the current module. It means that other parts of the application can import and use this component.
In summary, this code defines a React functional component named Home
that represents the main content area of a home page in a web application. It receives a posts
prop, and based on the length of the posts
array, it either renders the Feed
component to display the posts or a message indicating that there are no posts to display yet. Other components can import and use this Home
component to render the main content of the home page.
Create Feed Component
Next we will write the logic to display the posts feed by creating a new file Feed.js file and passing the posts as props in the following way:
import Post from "./Post";
const Feed = ({ posts }) => {
return (
<>
{posts.map((post) => (
<Post key={post.id} post={post} />
))}
</>
);
};
export default Feed;
The above code defines a React functional component named Feed
that represents a feed or list of posts in a web application. Here's an explanation of the code:
- Importing Dependencies:
Post
: This import statement is used to import thePost
component from a separate file. It suggests that there is a file namedPost.js
that defines thePost
component.
- Function Signature:
const Feed = ({ posts }) => { ... }
: This defines a functional component namedFeed
using arrow function syntax. It accepts a single prop namedposts
, which represents an array of post data.
- Rendering the Feed:
- The component's body returns JSX code that represents the structure and content of the feed.
<>...</>
: This is a React fragment, denoted by empty angle brackets (<>...</>
). It allows returning multiple elements without the need for an additional wrapping element.{posts.map((post) => ( ... ))}
: This uses themap
method to iterate over eachpost
object in theposts
array and returns a new array of JSX elements.<Post key={post.id} post={post} />
: This renders thePost
component for eachpost
in theposts
array. Thekey
prop is set to theid
property of the post object to provide a unique identifier for React's reconciliation process. Thepost
object is passed as a prop to thePost
component.
- Exporting the Feed Component:
export default Feed;
: This exports theFeed
component as the default export of the current module. It means that other parts of the application can import and use this component.
In summary, this code defines a React functional component named Feed
that represents a feed or list of posts in a web application. It receives a posts
prop, which is an array of post data. The component iterates over the posts
array using the map
method and renders the Post
component for each post, passing the post data as a prop. Other components can import and use this Feed
component to render a list of posts in the application's feed or similar sections.
Create Post Component
Next, we will create a Post.js
file and write the logic for an individual post with the post props passed in the following way:
import { Link } from "react-router-dom";
const Post = ({ post }) => {
return (
<article className="post">
<Link to={`/post/${post.id}`}>
<h2>{post.title}</h2>
<p className="postDate">{post.datetime}</p>
</Link>
<p className="postBody">
{post.body.length <= 25 ? post.body : `${post.body.slice(0, 25)}...`}
</p>
</article>
);
};
export default Post;
The above code defines a React functional component named Post
that represents a single post within a web application. Here's an explanation of the code:
- Importing Dependencies:
Link
: This import statement is used to import theLink
component from thereact-router-dom
library. TheLink
component is used to create links for navigation within the application.
- Function Signature:
const Post = ({ post }) => { ... }
: This defines a functional component namedPost
using arrow function syntax. It accepts a single prop namedpost
, which represents the data of a single post.
- Rendering the Post:
- The component's body returns JSX code that represents the structure and content of a single post.
<article className="post">
: This is an HTML<article>
element with the CSS class name "post". It serves as the container for the post content.<Link to={/post/${[post.id](<http://post.id/>)}}>
: This uses theLink
component to create a link to a specific post using the post'sid
property. The URL is generated dynamically using template literals.<h2>{post.title}</h2>
: This is an HTML heading level 2 element (<h2>
) that displays the title of the post. The title is obtained from thepost
prop.<p className="postDate">{post.datetime}</p>
: This is an HTML paragraph element (<p>
) with the CSS class name "postDate". It displays the datetime of the post. The datetime is obtained from thepost
prop.<p className="postBody">...</p>
: This is an HTML paragraph element (<p>
) with the CSS class name "postBody". It displays the body content of the post. If the length of the body content is less than or equal to 25 characters, the entire body is displayed. Otherwise, only the first 25 characters followed by an ellipsis ("...") are displayed. This is achieved using a conditional expression (post.body.length <= 25 ? post.body :
${post.body.slice(0, 25)}...``)`
- Exporting the Post Component:
export default Post;
: This exports thePost
component as the default export of the current module. It means that other parts of the application can import and use this component.
In summary, this code defines a React functional component named Post
that represents a single post within a web application. It receives a post
prop, which contains the data for a post. The component renders the post's title, datetime, and a truncated version of the post's body. It also wraps the content in a Link
component, allowing the post to be clickable and navigate to a specific post page. Other components can import and use this Post
component to render individual posts in the application.
Once this is done, our Home page will display a list of blog posts.
Create PostPage Component
Next we will write the logic to display individual post content on the PostPage.js
file in the following way:
import { useParams, Link } from "react-router-dom";
const PostPage = ({ posts, handleDelete }) => {
const { id } = useParams();
const post = posts.find((post) => post.id.toString() === id);
return (
<main className="PostPage">
<article className="post">
{post && (
<>
<h2>{post.title}</h2>
<p className="postDate">{post.datetime}</p>
<p className="postBody">{post.body}</p>
<button onClick={() => handleDelete(post.id)}>Delete Post</button>
</>
)}
{!post && (
<>
<h2>Post Not Found</h2>
<p>Well, that's disappointing</p>
<p>
<Link to="/">Visit Our Home Page</Link>
</p>
</>
)}
</article>
</main>
);
};
export default PostPage;
The above code defines a React functional component named PostPage
that represents a single post page within a web application. Here's an explanation of the code:
- Importing Dependencies:
useParams
: This import statement is used to import theuseParams
hook from thereact-router-dom
library. TheuseParams
hook allows accessing the parameters of the current route.
- Function Signature:
const PostPage = ({ posts, handleDelete }) => { ... }
: This defines a functional component namedPostPage
using arrow function syntax. It accepts two props:posts
andhandleDelete
. Theposts
prop represents an array of post data, andhandleDelete
is a function to handle the deletion of a post.
- Accessing Post Data:
- The component uses the
useParams
hook to retrieve theid
parameter from the current route. Theid
parameter represents the unique identifier of the post being displayed. This is achieved with the lineconst { id } = useParams();
. - The component then searches the
posts
array for a post whoseid
matches theid
parameter. It uses thefind
method with a callback function to perform the search:const post = posts.find((post) => post.id.toString() === id);
.
- The component uses the
- Rendering the Post Page:
- The component's body returns JSX code that represents the structure and content of the post page.
<main className="PostPage">
: This is an HTML<main>
element with the CSS class name "PostPage". It serves as the container for the main content of the post page.<article className="post">
: This is an HTML<article>
element with the CSS class name "post". It serves as the container for the post content.{post && ...}
: This is a conditional rendering statement that checks if apost
object is found based on the providedid
. If a post is found, the content inside the curly braces is rendered. Otherwise, the content inside the{!post && ...}
block is rendered.<h2>{post.title}</h2>
,<p className="postDate">{post.datetime}</p>
,<p className="postBody">{post.body}</p>
: These JSX elements display the title, datetime, and body of the post, respectively. The values are obtained from thepost
object.<button onClick={() => handleDelete()}>Delete Post</button>
: This is a button element that triggers thehandleDelete
function when clicked. It allows deleting the post.{!post && ...}
: This is the conditional rendering block that is executed when no post is found based on the providedid
.<h2>Post Not Found</h2>
,<p>Well, that's disappointing</p>
,<p>...</p>
: These JSX elements display a message indicating that the post was not found. It provides an option to visit the home page using aLink
component.<Link to="/">Visit Our Home Page</Link>
: This is aLink
component provided byreact-router-dom
that creates a link to the home page of the application.
- Exporting the PostPage Component:
export default PostPage;
: This exports thePostPage
component as the default export of the current module. It means that other parts of the application can import and use this component.
In summary, this code defines a React functional component named PostPage
that represents a single post page within a web application.
The above component will only function when we will pass the necessary props from the App.js
component in the following way:
<Route
path=":id"
element={<PostPage posts={posts} handleDelete={handleDelete} />}
/>
</Route>
Next, we will write the logic for the handleDelete
function in the App.js
file in the following way:
const navigate = useNavigate();
const handleDelete = (id) => {
const postsList = posts.filter((post) => post.id !== id);
setPosts(postsList);
navigate("/");
};
The above code snippet shows a React component that includes a handleDelete
function and uses the useNavigate
hook from the react-router-dom
library. Here's an explanation of the code:
const navigate = useNavigate();
: This line declares a constant variablenavigate
and assigns it the value returned by theuseNavigate
hook. TheuseNavigate
hook is a utility provided byreact-router-dom
that returns a navigation function. This function can be used to programmatically navigate to different routes within the application.const handleDelete = (id) => { ... }
: This declares an arrow function namedhandleDelete
that takes anid
parameter. This function is responsible for handling the deletion of a post.const postsList = posts.filter((post) => post.id !== id);
: This line filters theposts
array to create a new array calledpostsList
that excludes the post with the specifiedid
. Thefilter
method is used, and it checks if each post'sid
is not equal to the providedid
parameter.setPosts(postsList);
: This line suggests that there is a state variable namedposts
(presumably declared using theuseState
hook) and a correspondingsetPosts
function to update that state. It sets the value ofposts
to the filteredpostsList
, effectively removing the post with the specifiedid
from the list.navigate("/");
: This line uses thenavigate
function from theuseNavigate
hook to navigate to the root or home page of the application. It redirects the user to the specified route.
In summary, the code defines a handleDelete
function that filters the posts
array to remove the post with the given id
, updates the posts
state with the filtered array, and then navigates to the root or home page of the application.
Next, we will pass some states as props for the New Post page in App.js
page in the following way:
const [postTitle, setPostTitle] = useState("");
const [postBody, setPostBody] = useState("");
const handleSubmit = () => {};
<Route
index
element={
<NewPost
handleSubmit={handleSubmit}
postTitle={postTitle}
postBody={postBody}
setPostTitle={setPostTitle}
setPostBody={setPostBody}
/>
}
/>
Create NewPost Component
Now we will create the NewPost.js
code for displaying a form where users can create a new blog post in the following way:
const NewPost = ({
handleSubmit,
postTitle,
setPostTitle,
postBody,
setPostBody,
}) => {
return (
<main className="NewPost">
<h2>New Post</h2>
<form className="newPostForm" onSubmit={handleSubmit}>
<label htmlFor="postTitle">Title: </label>
<input
type="text"
required
value={postTitle}
onChange={(e) => setPostTitle(e.target.value)}
id="postTitle"
/>
<label htmlFor="postBody">Post: </label>
<textarea
type="text"
id="postBody"
required
value={postBody}
onChange={(e) => setPostBody(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
</main>
);
};
export default NewPost;
The above code defines a React functional component named NewPost
that represents a form for creating a new post. Here's an explanation of the code:
- Function Signature:
const NewPost = ({ handleSubmit, postTitle, setPostTitle, postBody, setPostBody }) => { ... }
: This defines a functional component namedNewPost
using arrow function syntax. It accepts several props:handleSubmit
,postTitle
,setPostTitle
,postBody
, andsetPostBody
. These props are used to handle form submission and manage the state of the post title and body.
- Rendering the New Post Form:
- The component's body returns JSX code that represents the structure and content of the new post form.
<main className="NewPost">
: This is an HTML<main>
element with the CSS class name "NewPost". It serves as the container for the main content of the new post form.<h2>New Post</h2>
: This is an HTML heading element that displays the text "New Post" as the title of the form.<form className="newPostForm" onSubmit={handleSubmit}>
: This is an HTML<form>
element with the CSS class name "newPostForm". It represents the form for creating a new post and has anonSubmit
event handler that calls thehandleSubmit
function when the form is submitted.<label htmlFor="postTitle">Title: </label>
: This is an HTML<label>
element associated with the post title input field. It displays the text "Title:" as the label for the input.<input type="text" required value={postTitle} onChange={(e) => setPostTitle(e.target.value)} id="postTitle" />
: This is an HTML<input>
element of type "text" that represents the input field for the post title. It is a controlled component, meaning its value is controlled by thepostTitle
prop and updated through thesetPostTitle
function when the user types in the input field.<label htmlFor="postBody">Post: </label>
: This is an HTML<label>
element associated with the post body textarea. It displays the text "Post:" as the label for the textarea.<textarea type="text" id="postBody" required value={postBody} onChange={(e) => setPostBody(e.target.value)} />
: This is an HTML<textarea>
element that represents the input field for the post body. Similar to the post title input, it is a controlled component with its value controlled by thepostBody
prop and updated through thesetPostBody
function.<button type="submit">Submit</button>
: This is an HTML<button>
element of type "submit" that represents the submit button for the form. When clicked, it triggers the form submission and calls thehandleSubmit
function provided as a prop.
- Exporting the NewPost Component:
export default NewPost;
: This exports theNewPost
component as the default export of the current module. It means that other parts of the application can import and use this component.
In summary, this code defines a React functional component NewPost
that represents a form for creating a new post. It includes input fields for the post title and body, and a submit button to trigger the form submission. The values of the input fields are controlled by props (postTitle
and postBody
) and can be updated through corresponding setter functions (setPostTitle
and setPostBody
). When the form is submitted, the provided handleSubmit
function is called to handle the form submission
logic.
Create Submit Function
Next we will write the logic for the handleSubmit
function and install the date package using the command, npm install date-fns --save
in the App.js
file in the following way:
import { format } from "date-fns";
const handleSubmit = (e) => {
e.preventDefault();
const id = posts.length ? posts[posts.length - 1] + 1 : 1;
//npm install date-fns --save
const datetime = format(new Date(), "MMMM dd, yyyy pp");
const newPost = { id, title: postTitle, datetime, body: postBody };
const allPosts = [...posts, newPost];
setPosts(allPosts);
setPostTitle("");
setPostBody("");
navigate("/");
};
The above code snippet defines a handleSubmit
function used for handling the form submission in a post creation scenario. Here's an explanation of the code:
- Importing the
format
function from the "date-fns" library:import { format } from "date-fns";
: This line imports theformat
function from the "date-fns" library. Theformat
function is a utility function that helps format dates according to specific patterns.
- The
handleSubmit
function:const handleSubmit = (e) => { ... }
: This line declares an arrow function namedhandleSubmit
that takes an event objecte
as its parameter. The function is triggered when the form is submitted.
- Preventing default form submission behavior:
e.preventDefault();
: This line calls thepreventDefault()
method on the event objecte
. It prevents the default behavior of form submission, which includes a page refresh.
- Generating a unique ID for the new post:
const id = posts.length ? posts[posts.length - 1] + 1 : 1;
: This line checks if there are existing posts (posts.length > 0
). If there are, it assigns a new ID by incrementing the ID of the last post in theposts
array (posts[posts.length - 1] + 1
). Otherwise, if there are no existing posts, it assigns the ID as1
.
- Formatting the current date and time:
const datetime = format(new Date(), "MMMM dd, yyyy pp");
: This line uses theformat
function from the "date-fns" library to format the current date and time. It creates a newDate
object withnew Date()
and formats it as a string using the pattern "MMMM dd, yyyy pp". The pattern represents the month (MMMM), day (dd), year (yyyy), and time (pp in a localized format).
- Creating a new post object:
const newPost = { id, title: postTitle, datetime, body: postBody };
: This line creates a new object namednewPost
with propertiesid
,title
,datetime
, andbody
. The values of these properties are derived from the form input fields:id
from the generated ID,title
from thepostTitle
state,datetime
from the formatted date and time, andbody
from thepostBody
state.
- Updating the state with the new post:
const allPosts = [...posts, newPost];
: This line creates a new array namedallPosts
by spreading the existingposts
array and adding thenewPost
object to it.
- Updating the state and clearing the form:
setPosts(allPosts);
: This line calls thesetPosts
function (presumably from the state hook) to update the state variableposts
with theallPosts
array, which includes the new post.setPostTitle("");
: This line calls thesetPostTitle
function to clear thepostTitle
state, setting it to an empty string.setPostBody("");
: This line calls thesetPostBody
function to clear thepostBody
state, setting it to an empty string.
- Navigating to the home page:
navigate("/");
: This line likely uses thenavigate
function (presumably from theuseNavigate
hook) to navigate to the home page ("/") after the form submission. It redirects the user to the specified route.
In summary, this code defines a handleSubmit
function that handles the form submission for creating a new post. It generates a unique ID, formats the current date and time, creates a new post object, updates the state with the new post, clears the form input fields, and navigates to the home page.
Create Search Feature
Next, we will build the search functionality within the App.js
file in the following way:
const [search, setSearch] = useState([]);
const [searchResults, setSearchResults] = useState([]);
useEffect(() => {
const filteredResults = posts.filter(
(post) =>
post.body.toLowerCase().includes(search) ||
post.title.toLowerCase().includes(search)
);
setSearchResults(filteredResults.reverse());
}, [posts, search]);
The above code snippet demonstrates the usage of the useState
and useEffect
hooks in a React component. Here's an explanation of the code:
- State variables:
const [search, setSearch] = useState([]);
: This line initializes a state variablesearch
using theuseState
hook. The initial value ofsearch
is an empty array ([]
). It represents the search query entered by the user.const [searchResults, setSearchResults] = useState([]);
: This line initializes another state variablesearchResults
using theuseState
hook. The initial value ofsearchResults
is an empty array ([]
). It represents the filtered search results based on the search query.
- The
useEffect
hook:useEffect(() => { ... }, [posts, search]);
: This hook is used to perform side effects in the component. It takes a callback function as its first argument and a dependency array as its second argument. The callback function will be executed when any of the dependencies in the array (posts
andsearch
) change.
- Filtering the search results:
const filteredResults = posts.filter(...);
: This line filters theposts
array based on the search query entered by the user. It uses thefilter
method to iterate over eachpost
object in theposts
array and checks if the lowercase version of the post's body or title includes the lowercase version of the search query.
- Updating the search results state:
setSearchResults(filteredResults.reverse());
: This line updates thesearchResults
state variable by setting it to thefilteredResults
array. Additionally, thereverse
method is called to reverse the order of the filtered results. This ensures that the most recent matching posts appear first in the search results.
Overall, this code sets up state variables for storing the search query and search results. It uses the useEffect
hook to update the search results whenever the posts
or search
dependencies change. The filtered search results are stored in the searchResults
state variable after being reversed.
Also, we will pass the searchResults
state variable as props in the Home component in the Route JSX in App.js
in the following way:
<Route index element={<Home posts={searchResults} />} />
Create Error Page Component
With this, we are done with the major portion to the application and now we will write the logic for the Missing.js
page which will appear if the page doesn’t exist in the following way:
import { Link } from "react-router-dom";
const Missing = () => {
return (
<main className="Missing">
<h2>Page Not Found</h2>
<p>Well, that's disappointing</p>
<p>
<Link to="/">Visit Our Home Page</Link>
</p>
</main>
);
};
export default Missing;
The above code defines a React component named Missing
, which represents a page that is not found (404 error). Here's a brief explanation of the code:
- Importing the
Link
component from "react-router-dom":import { Link } from "react-router-dom";
: This line imports theLink
component from the "react-router-dom" library. TheLink
component is used to create links within the application.
- The
Missing
component:const Missing = () => { ... }
: This line defines a functional component namedMissing
using arrow function syntax. The component has no props and doesn't receive any data from its parent component.
- Returning JSX:
- The component returns JSX elements that represent the content of the "Page Not Found" page. It includes:
<main className="Missing">
: Amain
element with the CSS class name "Missing".<h2>Page Not Found</h2>
: A heading displaying the text "Page Not Found".<p>Well, that's disappointing</p>
: A paragraph displaying the text "Well, that's disappointing".<p><Link to="/">Visit Our Home Page</Link></p>
: A paragraph containing aLink
component that links to the home page ("/"). When clicked, it will navigate the user to the home page.
- The component returns JSX elements that represent the content of the "Page Not Found" page. It includes:
- Exporting the component:
export default Missing;
: This line exports theMissing
component as the default export of the module. It allows other parts of the application to import and use theMissing
component.
In summary, this code defines a React component called Missing
that represents a page not found. It displays a message and provides a link to the home page for the user to navigate back.
Create Footer Component
Next, we will write a simple piece of code for the Footer.js
file to display the footer in the following way:
const Footer = () => {
const today = new Date();
return (
<footer className="Footer">
<p>Copyright © {today.getFullYear()}</p>
</footer>
);
};
export default Footer;
The above code defines a functional component named Footer
in React. Here's a breakdown of the code:
- The
Footer
component:const Footer = () => { ... }
: This line defines a functional component namedFooter
using arrow function syntax. The component has no props and doesn't receive any data from its parent component.
- Getting the current year:
const today = new Date();
: This line creates a newDate
object, which represents the current date and time.
- Returning JSX:
- The component returns JSX elements that represent the content of the footer section. It includes:
<footer className="Footer">
: Afooter
element with the CSS class name "Footer".<p>Copyright © {today.getFullYear()}</p>
: A paragraph displaying the copyright symbol ("©") and the current year obtained fromtoday.getFullYear()
. This ensures that the year displayed in the footer will always be the current year.
- The component returns JSX elements that represent the content of the footer section. It includes:
- Exporting the component:
export default Footer;
: This line exports theFooter
component as the default export of the module. It allows other parts of the application to import and use theFooter
component.
In summary, this code defines a React component called Footer
that represents the footer section of a webpage. It displays the current year in the copyright notice.
Lastly, we will write some simple code for the About.js
page in the following way:
const About = () => {
return (
<main className="About">
<h2>About</h2>
<p style={{ marginTop: "1rem" }}>
This blog app is a project to learn and master the fundamentals of the
React Framework.
</p>
</main>
);
};
export default About;
The above code defines a functional component named About
in React. Here's a breakdown of the code:
- The
About
component:const About = () => { ... }
: This line defines a functional component namedAbout
using arrow function syntax. The component has no props and doesn't receive any data from its parent component.
- Returning JSX:
- The component returns JSX elements that represent the content of the About page. It includes:
<main className="About">
: Amain
element with the CSS class name "About".<h2>About</h2>
: A heading displaying the text "About".<p style={{ marginTop: "1rem" }}>... </p>
: A paragraph displaying a description of the blog app project. The description is wrapped inside thestyle
attribute with inline CSS to set a margin-top of "1rem" for the paragraph.
- The component returns JSX elements that represent the content of the About page. It includes:
- Exporting the component:
export default About;
: This line exports theAbout
component as the default export of the module. It allows other parts of the application to import and use theAbout
component.
In summary, this code defines a React component called About
that represents the About page of a blog app. It displays a heading and a paragraph describing the purpose of the blog app project. The component's JSX code is responsible for rendering the content to be displayed on the About page.
Conclusion
In conclusion, React Routing is a powerful tool that enables developers to create seamless and dynamic user experiences in web applications. This blog post has guided you through the process of using React Routing to build a simple yet impressive blog site. By understanding the importance of responsive navigation and implementing routing, you now have the ability to create engaging and interactive blog sites. Take what you've learned, continue exploring the capabilities of React Routing, and enhance your web development skills. Happy coding!