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