Chapter 20 - Create Your Own Advanced Blog with React: A Step-by-Step Guide

A blog app using React, Redux, and styled-components. Features include routing, form validation, state management, and markdown support. Create, view, and style posts easily.

Chapter 20 - Create Your Own Advanced Blog with React: A Step-by-Step Guide

Let’s dive into building a simple blog application with some cool advanced features. Trust me, it’s gonna be fun!

Ever wanted to create your own blog but didn’t know where to start? Well, you’re in the right place! We’re going to walk through the process of building a blog application from scratch. And we’re not just talking about a basic, bare-bones blog. Oh no, we’re going all out with fancy features like routing, form validation, and state management. Exciting, right?

First things first, let’s talk about the tech stack we’ll be using. For this project, we’ll go with React for the frontend and Node.js with Express for the backend. Why? Because they’re awesome, that’s why! Plus, they play really well together.

Let’s start with setting up our project. Open up your terminal and let’s create a new React app:

npx create-react-app my-blog-app
cd my-blog-app

Now that we have our basic React app set up, let’s add some routing. We’ll use React Router for this. It’s like a traffic cop for your app, directing users to the right pages. Install it with:

npm install react-router-dom

Next, let’s set up our basic route structure. We’ll need routes for the home page, individual blog posts, and a page to create new posts. Open up your App.js file and let’s add some routes:

import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Home from './components/Home';
import Post from './components/Post';
import CreatePost from './components/CreatePost';

function App() {
  return (
    <Router>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/post/:id" component={Post} />
        <Route path="/create" component={CreatePost} />
      </Switch>
    </Router>
  );
}

export default App;

Cool, now we have our basic routing set up. But what about those components we’re routing to? Let’s create them!

First, let’s make a simple Home component that will display a list of blog posts:

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

function Home() {
  // We'll fetch posts from an API later
  const posts = [
    { id: 1, title: 'My First Blog Post' },
    { id: 2, title: 'React is Awesome' },
  ];

  return (
    <div>
      <h1>Welcome to My Blog</h1>
      {posts.map(post => (
        <div key={post.id}>
          <Link to={`/post/${post.id}`}>{post.title}</Link>
        </div>
      ))}
      <Link to="/create">Create New Post</Link>
    </div>
  );
}

export default Home;

Now, let’s create a Post component to display individual blog posts:

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

function Post() {
  const { id } = useParams();
  // We'll fetch the post data from an API later
  const post = { id, title: 'My Blog Post', content: 'This is the content of my blog post.' };

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

export default Post;

And finally, let’s create a CreatePost component with a form for creating new posts:

import React, { useState } from 'react';

function CreatePost() {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    // We'll handle form submission later
    console.log({ title, content });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="Enter post title"
      />
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="Enter post content"
      />
      <button type="submit">Create Post</button>
    </form>
  );
}

export default CreatePost;

Great! We now have the basic structure of our blog application. But remember when I said we’d add some fancy features? Let’s get to that!

First up: form validation. We don’t want people submitting empty blog posts, do we? Let’s add some simple validation to our CreatePost component:

import React, { useState } from 'react';

function CreatePost() {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [errors, setErrors] = useState({});

  const validateForm = () => {
    let errors = {};
    if (!title.trim()) errors.title = 'Title is required';
    if (!content.trim()) errors.content = 'Content is required';
    return errors;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const formErrors = validateForm();
    if (Object.keys(formErrors).length === 0) {
      // Form is valid, submit it
      console.log({ title, content });
    } else {
      // Form has errors, update state
      setErrors(formErrors);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="Enter post title"
      />
      {errors.title && <p>{errors.title}</p>}
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="Enter post content"
      />
      {errors.content && <p>{errors.content}</p>}
      <button type="submit">Create Post</button>
    </form>
  );
}

export default CreatePost;

Now our form will show error messages if someone tries to submit an empty title or content. Pretty neat, huh?

Next up: state management. As our app grows, managing state can become a bit of a headache. That’s where Redux comes in handy. It’s like a central brain for your app’s data. Let’s add Redux to our project:

npm install redux react-redux @reduxjs/toolkit

Now, let’s create a simple Redux store for our blog posts. Create a new file called store.js:

import { configureStore, createSlice } from '@reduxjs/toolkit';

const postsSlice = createSlice({
  name: 'posts',
  initialState: [],
  reducers: {
    addPost: (state, action) => {
      state.push(action.payload);
    },
  },
});

export const { addPost } = postsSlice.actions;

export const store = configureStore({
  reducer: {
    posts: postsSlice.reducer,
  },
});

Now we can use this store in our app. Update your index.js to provide the store to your app:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Great! Now we can use Redux in our components. Let’s update our Home component to use the Redux store:

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

function Home() {
  const posts = useSelector(state => state.posts);

  return (
    <div>
      <h1>Welcome to My Blog</h1>
      {posts.map(post => (
        <div key={post.id}>
          <Link to={`/post/${post.id}`}>{post.title}</Link>
        </div>
      ))}
      <Link to="/create">Create New Post</Link>
    </div>
  );
}

export default Home;

And let’s update our CreatePost component to dispatch the addPost action when a new post is created:

import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { addPost } from './store';

function CreatePost() {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [errors, setErrors] = useState({});
  const dispatch = useDispatch();

  // ... (previous code for validation)

  const handleSubmit = (e) => {
    e.preventDefault();
    const formErrors = validateForm();
    if (Object.keys(formErrors).length === 0) {
      // Form is valid, dispatch action
      dispatch(addPost({ id: Date.now(), title, content }));
      // Clear form
      setTitle('');
      setContent('');
    } else {
      // Form has errors, update state
      setErrors(formErrors);
    }
  };

  // ... (rest of the component)
}

export default CreatePost;

Awesome! Now we have a fully functioning blog application with routing, form validation, and state management. But wait, there’s more!

Let’s add some pizzazz to our blog with some styling. We’ll use styled-components for this. First, install it:

npm install styled-components

Now, let’s add some basic styling to our Home component:

import React from 'react';
import { Link } from 'react-router-dom';
import { useSelector } from 'react-redux';
import styled from 'styled-components';

const HomeWrapper = styled.div`
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
`;

const Title = styled.h1`
  color: #333;
  text-align: center;
`;

const PostLink = styled(Link)`
  display: block;
  margin: 10px 0;
  padding: 10px;
  background-color: #f0f0f0;
  color: #333;
  text-decoration: none;
  border-radius: 5px;

  &:hover {
    background-color: #e0e0e0;
  }
`;

const CreatePostLink = styled(Link)`
  display: inline-block;
  margin-top: 20px;
  padding: 10px 20px;
  background-color: #007bff;
  color: white;
  text-decoration: none;
  border-radius: 5px;

  &:hover {
    background-color: #0056b3;
  }
`;

function Home() {
  const posts = useSelector(state => state.posts);

  return (
    <HomeWrapper>
      <Title>Welcome to My Blog</Title>
      {posts.map(post => (
        <PostLink key={post.id} to={`/post/${post.id}`}>{post.title}</PostLink>
      ))}
      <CreatePostLink to="/create">Create New Post</CreatePostLink>
    </HomeWrapper>
  );
}

export default Home;

Look at that! Our blog is starting to look pretty snazzy.

Now, let’s add one more advanced feature: markdown support for our blog posts. This will allow users to format their posts with headings, bold text, links, and more. We’ll use the react-markdown library for this. First, install it:

npm install react-markdown

Now, let’s update our Post component to render markdown content:

import React from 'react';
import { useParams } from 'react-router-dom';
import { useSelector } from 'react-redux';
import ReactMarkdown from 'react-markdown';
import styled from 'styled-components';

const PostWrapper