How to Build a Blog App with React and Tailwind CSS - Part 3

In the previous article we saw how to create the Header Footer and the About sections for our site. In this article e shall create the remaining components and complete our front end for a blog app.

How To Create the useFetch Custom Hook

useFetch is a custom hook created for this project to fetch articles from the JSON server. The reason for this is that many components will rely on this component to get data from the server. We use the useState hook to ensure the state is managed in our app when the state changes the User Interface reflects the change and useEffect hook to perform side effects which in our case is fetching from the JSON server.

import { useEffect, useState } from 'react';

const useFetch=(url)=>{

    const [data,setData] = useState([]);
    const [isPending,setIsPending] = useState(true);
    const [error, setError] = useState(null);

    useEffect(()=>{

            fetch(url)
            .then((res) =>  res.json())
            .then((data) =>{

                setData(data);
                setIsPending(false);
                setError(null);
            })
            .catch((err)=>{
                if(err.name==='AbortError'){
                    console.log('Fetch aborted');
                }
                else{
                    setError(err.message);
                    setIsPending(false);
                }
        })

    },[url]);

    return { data, isPending, error };

}

export default useFetch;

How to Create the Article List Component

This component is used to display the articles and they are styled in a grid to make the neat and easy on the eye when the user glances at our Home page.

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

 const ArticleList = ( { articles } ) =>{
     return(
        <div className='p-8  divide-x divide-y grid grid-cols-4 gap-4'>

                { articles.map(article =>(
                            <div className='p-8 ' key={article.id}>
                                <Link to={`/articles/${article.id}`}>
                                    <h2 className='font-sans text-2xl text-gray-600 font-bold'>{article.title}</h2>
                                </Link>
                                <p>Written by {article.author}</p>
                                </div>
                        ))}
        </div>
     );
 }


export default ArticleList;

How to Create the Home Component

This component makes up our home page and where all the articles written are displayed for our audience to view. we use the custom hook,useFetch, to get the articles from the JSON server as a prop and use the Article List component to display the articles.

import ArticleList from "./ArticleList";
import useFetch from "../hooks/useFetch";
import React from "react";

function Home(){
    const {data: articles, isPending, error} = useFetch('http://localhost:3001/articles');

    return(
        <div className="home">
            { error && <div>{error}</div>}
            { isPending && <div>Loading...</div>}
            <div>
                <h2 className="text-center mt-4 text-4xl text-gray-600 font-bold">All Posts</h2>
            <ArticleList  articles={articles} />
            </div>
        </div>
    );

}

export default Home;

How to Create the Article Component

This component is where we display the whole article and enable our audience to read articles. There is also a delete button where the writer can delete the article. As of now, there is no user authentication and anyone can delete the article. We use the useNavigate and useParam hooks to enable programmatic navigation. Once the writer deletes an article they will be taken to the homepage and view other articles. To ensure the deletion of an article we concatenate (add) the id to the URL to ensure the deletion of the particular article from the JSON server.

import React  from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import useFetch from '../hooks/useFetch';



function Article() {
    const { id } = useParams();
    // const [articles,setArticles]= useState('');
    const { data: article,error,isPending } = useFetch(`http://localhost:3001/articles/${id}`);
    const navigate = useNavigate();
    console.log(id)
    console.log(article)
    console.log(error)


    const handleClick = () =>{
        console.log(id)
        fetch('http://localhost:3001/articles/' + id,{
            method:'DELETE'
        })
        .then((res)=> res.json())
        .then(() => {
            navigate('/');
        })
    }
    return(
        <div className='article-detail'>
            {isPending && <div>Loading...</div>}
            {error && <div>{error}</div>}
            <article className='p-8'>
            <button className="bg-red-600 p-2 border border-gray-400 rounded-full text-white" onClick={handleClick}>Delete Article</button>
                <h2 className="text-3xl text-gray-600 font-bold text-center pb-4 font-sans">{ article.title }</h2>
                <p className='font-sans text-gray-600 font-bold pb-4'>Written by { article.author }</p>
                <div className='font-sans text-gray-600 pb-4'>{ article.body }</div>

            </article>

        </div>
    );


}

export default Article;

How to Create the Add New Component

Here we shall create a POST request and the form where the user can submit an article. The useState hook is used to update the User Interface when a change is made and thus we use it for the title, author, and body of the article. The useNavigate hook is used for the programmatic navigation of routes in the app, it will send us to the home page as soon as we click add blog button at the end of the form.

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';


function AddNew() {
    const [title, setTitle] = useState('');
    const [body, setBody] = useState('');
    const [author, setAuthor] = useState('');
    const [isPending,setisPending] = useState(false);
    const navigate = useNavigate();

    const handleSubmit = (e) =>  {
        e.preventDefault();
        const article ={ title, body, author };
        setisPending(true);

        fetch('http://localhost:3001/articles',{
            method:'POST',
            headers:{"Content-Type":"application/json"},
            body: JSON.stringify(article)
        })
        .then((res)=> res.json())
        .then(()=>{
            setAuthor('');
            setBody('');
            setTitle('');
            setisPending(false);
            navigate('/');
        })
    } 

    return(
        <div className="p-8">
            <h2 className='text-3xl text-gray-600 font-bold'>Add a New Post</h2>
            <form className='p-4 flex flex-col' onSubmit={handleSubmit}>
                <label className='pb-4 mt-4'>Article Title:</label>
                    <input className='p-2 border border-solid border-gray-300'
                    type="text"
                    required
                    value={title}
                    placeholder="Add a title"
                    onChange={(e)=>setTitle(e.target.value)}                    
                    />
                    <label className='pb-4 mt-4'>Article Author:</label>
                    <input className='p-2 border border-solid border-gray-300 '
                    type="text"
                    required
                    value={author}
                    placeholder="Add your name"
                    onChange={(e)=>setAuthor(e.target.value)} 
                />
                <label className='mt-4 pb-4'>Article Body: </label>
                <textarea className='py-10 border border-solid border-gray-300'
                    required
                    value={body}
                    onChange={(e) => setBody(e.target.value)}
                />

                {!isPending && <button className='mx-80 p-2 mt-6 border border-gray-400 rounded-full text-gray-600'>Add Blog</button>}
                {isPending && <button disabled>Adding Blog</button>}
            </form>

        </div>
    );


}
export default AddNew;

How to Route in the App Component

Routing from one component to another in React has undergone some changes as of react-router version 6. You can find out how to incorporate routing by reading this tutorial.

There are two methods to go about it where you can include Browser Router in the App.js or index.js. I have used the former as it worked best for me. Feel free to use whichever method works best.

Wrap the components that we will be routing in and leave out the Header and Footer components as they shall appear on all pages.

import React from "react";
import { Route, Routes } from "react-router-dom";
import Footer from "./Footer";
import Header from "./Header";
import Home from "./Home";
import About from "./About"
import AddNew from "./AddNew";
import Article from "./Article";


function App() {
  return (

    <div className="App">
      <Header />

        <Routes> 
          <Route path="/" element={<Home />} />
          <Route path="/addnew" element={<AddNew />} />       
          <Route path="/header" element={<Header />} />
          <Route path="/about" element={<About />} />
          <Route path="/footer" element={<Footer />} />
          <Route path="articles/:id" element={<Article />} />
        </Routes>
        <Footer />
      </div>


  );
}

export default App;

Include Browser Router to the index.js of your app.

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './components/App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter as Router } from "react-router-dom";

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Router>
    <App />
    </Router>
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

A brief recap of the things you can do now as a result of this project:

  • Get articles.

  • Delete articles.

  • Post articles.

  • Use tailwind CSS to style components.

  • Create routes to various components.

Feel free to share your comments, insights, and questions on the series so we can learn together. Here is a link to the repository on GitHub which may be subject to change such as deployment and additional features.

Until next time may the code be with you.