Day 23 – Mini Blog Project in React (Part 1)
8 mins read

Day 23 – Mini Blog Project in React (Part 1)

We’ve covered a lot of React concepts: props, hooks, forms, state management, routing, authentication. Now it’s time to bring them all together in a practical project.

👉 Over the next two blogs, we’ll build a Mini Blog Project in React.

  • Part 1 (today): Setup, display blog list, show blog details, add blog, edit blog, delete blog.
  • Part 2: Authentication (login/logout), protecting routes, and state management comparison (Context API, Redux Toolkit, Zustand).

We’ll use the modern Tailwind UI you’ve already seen (navbar, blog cards, footer) and turn it into a functional React project.


Step 1: Setup Project & Structure

We’ll use Vite for fast development.

npm create vite@latest mini-blog

This will ask you to choose many things like:

  • Okay to Proceed (Y): Enter y
  • Select a framework: Select React using Down and Up Arrow Key from the keyboard.
  • After that you have to select a variant from many language, choose JavaScript.

Run below commands

cd mini-blog
npm install
npm install react-router-dom tailwindcss
npx tailwindcss init -p

Update vite.config.js and index.css with Tailwind setup.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
})
@import "tailwindcss";

👉 Remove other files and make Folder structure:

src/
 ├── components/
 │    ├── Header.jsx
 │    ├── Footer.jsx
 │    ├── BlogCard.jsx

 ├── pages/
 │    ├── Home.jsx
 │    ├── BlogDetail.jsx
 │    ├── AddBlog.jsx
 │    ├── EditBlog.jsx

 ├── App.jsx
 └── main.jsx

Step 2: Setup React Router

In App.jsx:

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Header from "./components/Header";
import Footer from "./components/Footer";
import Home from "./pages/Home";
import BlogDetail from "./pages/BlogDetail";
import AddBlog from "./pages/AddBlog";
import EditBlog from "./pages/EditBlog";

function App() {
  return (
    <Router>
      <Header />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/blog/:id" element={<BlogDetail />} />
        <Route path="/add" element={<AddBlog />} />
        <Route path="/edit/:id" element={<EditBlog />} />
      </Routes>
      <Footer />
    </Router>
  );
}

export default App;

👉 We now have routes for home, blog detail, add blog, and edit blog.

Now update your main.jsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css"; 

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Now make components files.

Start with Header.jsx

import React from "react";
import { Link } from "react-router-dom";

export default function Header() {
  return (
    <header className="bg-white shadow">
      <div className="container mx-auto px-4 py-4 flex items-center justify-between">
        <Link to="/" className="flex items-center gap-3">
          <div className="w-10 h-10 bg-gradient-to-br from-indigo-500 to-pink-500 rounded-full flex items-center justify-center text-white font-bold">
            B
          </div>
          <span className="font-semibold text-lg">MiniBlog</span>
        </Link>

        <nav className="flex items-center gap-4">
          <Link to="/" className="text-gray-700 hover:text-indigo-600">
            Home
          </Link>
          <Link to="/add" className="text-gray-700 hover:text-indigo-600">
            Add Blog
          </Link>
          {/* placeholder for future login / profile */}
          <Link to="/login" className="px-3 py-1 rounded border border-gray-200 text-sm hover:bg-gray-50">
            Sign In
          </Link>
        </nav>
      </div>
    </header>
  );
}

Next create Footer.jsx

import React from "react";

export default function Footer() {
  return (
    <footer className="bg-gray-50 border-t mt-8">
      <div className="container mx-auto px-4 py-6 text-center text-sm text-gray-600">
        © {new Date().getFullYear()} MiniBlog • Built with React & Tailwind
      </div>
    </footer>
  );
}

And next is BlogCard.jsx component.

import React from "react";

export default function BlogCard({ id, title, body }) {
  // small excerpt
  const excerpt = body?.length > 120 ? `${body.slice(0, 120)}...` : body;

  return (
    <article className="bg-white rounded-lg shadow-sm overflow-hidden hover:shadow-md transition p-5 h-full flex flex-col">
      <h3 className="text-lg font-semibold mb-2">{title}</h3>
      <p className="text-gray-600 flex-1">{excerpt}</p>
      <div className="mt-4 text-sm text-gray-400">Post ID: {id}</div>
    </article>
  );
}

Step 3: Blog List (Home Page)

We’ll fetch posts from JSONPlaceholder API.

pages/Home.jsx:

import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import BlogCard from "../components/BlogCard";

function Home() {
  const [blogs, setBlogs] = useState([]);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts?_limit=6")
      .then((res) => res.json())
      .then((data) => setBlogs(data));
  }, []);

  return (
    <div className="p-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
      {blogs.map((blog) => (
        <Link key={blog.id} to={`/blog/${blog.id}`}>
          <BlogCard title={blog.title} body={blog.body} />
        </Link>
      ))}
    </div>
  );
}

export default Home;

👉 BlogCard is just a styled component for displaying each blog.


Step 4: Blog Detail Page

pages/BlogDetail.jsx:

import { useParams } from "react-router-dom";
import { useEffect, useState } from "react";

function BlogDetail() {
  const { id } = useParams();
  const [blog, setBlog] = useState(null);

  useEffect(() => {
    fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
      .then((res) => res.json())
      .then((data) => setBlog(data));
  }, [id]);

  if (!blog) return <p>Loading...</p>;

  return (
    <div className="p-6 max-w-2xl mx-auto">
      <h1 className="text-2xl font-bold mb-4">{blog.title}</h1>
      <p>{blog.body}</p>
    </div>
  );
}

export default BlogDetail;

Step 5: Add Blog (Form Handling)

pages/AddBlog.jsx:

import { useState } from "react";
import { useNavigate } from "react-router-dom";

function AddBlog({ blogs, setBlogs }) {
  const [title, setTitle] = useState("");
  const [body, setBody] = useState("");
  const navigate = useNavigate();

  const handleSubmit = (e) => {
    e.preventDefault();
    const newBlog = { id: Date.now(), title, body };
    setBlogs((prev) => [...prev, newBlog]);
    navigate("/");
  };

  return (
    <form onSubmit={handleSubmit} className="p-6 max-w-xl mx-auto space-y-4">
      <input
        type="text"
        placeholder="Blog Title"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        className="w-full p-2 border rounded"
      />
      <textarea
        placeholder="Blog Content"
        value={body}
        onChange={(e) => setBody(e.target.value)}
        className="w-full p-2 border rounded"
      />
      <button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
        Add Blog
      </button>
    </form>
  );
}

export default AddBlog;

👉 Controlled inputs with useState update our blog list.


Step 6: Edit & Delete Blog

pages/EditBlog.jsx:

import { useParams, useNavigate } from "react-router-dom";
import { useState } from "react";

function EditBlog({ blogs, setBlogs }) {
  const { id } = useParams();
  const navigate = useNavigate();
  const blog = blogs.find((b) => b.id === Number(id));

  const [title, setTitle] = useState(blog.title);
  const [body, setBody] = useState(blog.body);

  const handleSubmit = (e) => {
    e.preventDefault();
    setBlogs((prev) =>
      prev.map((b) => (b.id === blog.id ? { ...b, title, body } : b))
    );
    navigate(`/blog/${id}`);
  };

  const handleDelete = () => {
    setBlogs((prev) => prev.filter((b) => b.id !== blog.id));
    navigate("/");
  };

  return (
    <form onSubmit={handleSubmit} className="p-6 max-w-xl mx-auto space-y-4">
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        className="w-full p-2 border rounded"
      />
      <textarea
        value={body}
        onChange={(e) => setBody(e.target.value)}
        className="w-full p-2 border rounded"
      />
      <button type="submit" className="bg-green-500 text-white px-4 py-2 rounded">
        Save Changes
      </button>
      <button
        type="button"
        onClick={handleDelete}
        className="bg-red-500 text-white px-4 py-2 rounded ml-2"
      >
        Delete
      </button>
    </form>
  );
}

export default EditBlog;

Conclusion

Mini Blog App Screenshot

In this blog, we built the foundation of our Mini Blog Project in React:

  • Set up project with React Router + Tailwind.
  • Display blog list from API.
  • Blog detail page.
  • Add, Edit, Delete blog (form handling & state updates).

👉 In Part 2 (Day 24), we’ll add:

  • Authentication (Login/Logout flow)
  • Protected Routes
  • State management comparison (Context API, Redux Toolkit, Zustand).

That will complete our React 20-day learning journey with a real-world project. Click to find the Source code of Mini Blog Project in React.

Leave a Reply

Your email address will not be published. Required fields are marked *