React Ecosystem - Building a BlogPost application with React and React Hooks

1. Building a Blog Post application

Let's create a blog post application. It will have below features:

  1. Create a new project with npx create-react-app react-blog-posts --template typescript command.

  2. Create BlogPosts.tsx component under src/components folder and IBlogPost model under src/models.

import React from 'react';
import IBlogPost from '../models/IBlogPost';

interface IBlogPostsProps {
posts: Array<IBlogPost>
}


function BlogPosts(props: IBlogPostsProps) {
return (
<div className="blog-container">
<ul className="blog-posts">
{
props.posts.map(post => <li key={post.id}>{post.title}</li>)
}
</ul>
</div>
);
}

export default BlogPosts;
interface IBlogPost {
id: number
title: string
content: string
author: string
postedOn: string
tags: string[]
}

export default IBlogPost;
Explanation

We have created BlogPosts function which takes parameter of type IBlogPostsProps. This type contains array of posts of type IBlogPost. We are only showing title of the Blog Post in this component. Shortly, we will update this component and extract listing of BlogPosts as seperate component. For now, Let's update App.tsx and use BlogPosts to show dummy posts.

function App() {
return (
<div className="App-Container">
<BlogPosts posts={POSTS}/>
</div>
);
}

You can get the dummy posts array from here.

  1. Run the application npm run start and you will see the page loaded with post titles. React Blog Post App Page with Dummy posts

  2. Now, Let's create a new component BlogPost.tsx which will show the selected blog post.

import React from 'react';
import IBlogPost from '../models/IBlogPost';
import './BlogPost.css';

interface IBlogPostProps {
post: IBlogPost
}

function BlogPost(props: IBlogPostProps) {
const post = props.post
return (
<div className='blog-post'>
<div className='blog-post-title'>{post.title}</div>
<div className='blog-post-body'>{post.content}</div>
<div className='blog-post-footer'>
<div className='blog-author'>{`By ${post.author} at ${post.postedOn}`}</div>
<div className='blog-tags'>
<div key='tags-label'>Tags: </div>
{post.tags.map(tag => <div key={tag}>{tag}</div>)}
</div>
</div>
</div>
);
}

export default BlogPost;
  1. Create a new component BlogListing.tsx to list the available posts to read.
import React from 'react';

declare type IBlogPostData = {
id: number
title: string
}

interface IBlogListing {
blogPosts: IBlogPostData[]
selectedBlogPost: number
onClick: (id: number) => void
}

function BlogListing(props: IBlogListing) {
return(
<div className='blog-listing'>
<ul className="blog-posts">
{
props.blogPosts.map(post => <li className={props.selectedBlogPost === post.id ? 'active' : ''} key={post.id} onClick={() => props.onClick(post.id)}>{post.title}</li>)
}
</ul>
</div>
);
}

export default BlogListing;

In this component, we have declared IBlogPostData as type which holds id and title of the blog to be listed. This component takes collection of posts, selectedBlogPost(active post) and onClick function (action to perform when link is clicked) as arguments.

  1. Now, update BlogPosts.tsx component and use BlogListing and BlogPost in it.
function BlogPosts(props: IBlogPostsProps) {
/*1.*/const firsBlogPost = props.posts && props.posts.length > 0 ? props.posts[0] : null;
/*2.*/const [ selectedBlogPost, setSelectedBlogPost ] = useState<IBlogPost | null>(firsBlogPost);

/*3.*/function onBlogPostLinkClick(id: number): void {
const selectedBlogPost = props.posts.find(post => post.id === id);
setSelectedBlogPost(!!selectedBlogPost ? selectedBlogPost : null);
}

return (
<div className="blog-container">
<BlogListing
selectedBlogPost=
{selectedBlogPost?.id ?? 0}
blogPosts=
{props.posts.map(post => { return {id: post.id, title: post.title }})}
/*4.*/onClick=
{onBlogPostLinkClick}
/>
{!!selectedBlogPost ? <BlogPost post={selectedBlogPost}/>: null }
</div>
);
}

export default BlogPosts;

Explanation

  1. Run the application npm run start and you will see the page loaded with first post as selected as shown in below screenshot.

React Blog Post App with first selected post

  1. Now, we will add option to search blog posts either by title or tags. Create a component BlogSearch.tsx under src/components folder.
import React, { ChangeEvent } from 'react';
import { SearchType } from '../models/SearchType';

interface IBlogSearchProps {
searchText: string
selectedSearchOn: string
onSearchChange: (searchText: string, searchType: SearchType) => void
onSearchButtonClick: () => void
}

function BlogSearch(props: IBlogSearchProps) {
function onSearchTextChange(event: ChangeEvent<HTMLInputElement>): void {
props.onSearchChange(event.target.value, SearchType.SEARCH_TEXT)
}

function onSearchOnChange(event: ChangeEvent<HTMLSelectElement>): void {
props.onSearchChange(event.target.value, SearchType.SEARCH_ON)
}

return(
<div className="blog-search-container">
<div className='blog-search-title'>Search Blog</div>
<div className='blog-search-body'>
<input type="text" className="form-control" autoComplete="off" value={props?.searchText ?? ''} onChange={onSearchTextChange}/>
<select value={props.selectedSearchOn} className='form-control' onChange={onSearchOnChange}>
<option value='tag'>Tags</option>
<option value='title'>Title</option>
</select>
<button type="button" className="form-button" onClick={props.onSearchButtonClick}>Search</button>
</div>
</div>
);
}

export default BlogSearch;

Explanation

import React, { useState } from 'react';
import IBlogPost from '../models/IBlogPost';
import './BlogPosts.css';
import BlogListing from './BlogListing';
import BlogPost from './BlogPost';
import { SearchType } from '../models/SearchType';

interface IBlogPostsProps {
posts: Array<IBlogPost>
}


function BlogPosts(props: IBlogPostsProps) {
function findFirstPost(posts: Array<IBlogPost>) : IBlogPost | null {
return posts && posts.length > 0 ? posts[0] : null;
}

/*1.*/const [ posts, setPosts ] = useState(props.posts)
/*2.*/const [ showingPost, setShowingPost ] = useState<IBlogPost | null>(findFirstPost(posts));
/*3.*/const [ searchText, setSearchText ] = useState<string>('');
/*4.*/const [ selectedSearchOn, setSelectedSearchOn ] = useState<string>('tag')

/*5.*/function onBlogPostLinkClick(id: number): void {
const newShowingPost = posts.find(post => post.id === id);
setShowingPost(!!newShowingPost ? newShowingPost : null);
}

/*6.*/function onChangeHandler(value: string, searchType: SearchType) : void {
if (SearchType.SEARCH_TEXT === searchType) {
setSearchText(value)
} else {
setSelectedSearchOn(value)
}
}

function isMatched(value: string) {
return value.toLowerCase().includes(searchText.toLowerCase())
}

function filterPost(post: IBlogPost) {
if (selectedSearchOn === 'title') {
return isMatched(post.title)
} else {
return post.tags.some(isMatched)
}
}

/*7.*/function onSearch() {
if (searchText !== '') {
const foundPosts = props.posts.filter(filterPost)
setShowingPost(findFirstPost(foundPosts))
setPosts(foundPosts)
} else {
setShowingPost(findFirstPost(props.posts))
setPosts(props.posts)
}
}

return (
<div className="blog-container">
<BlogListing
showingPost={showingPost?.id ?? 0}
blogPosts={posts.map(post => { return {id: post.id, title: post.title }})}
onClick={onBlogPostLinkClick}
searchText={searchText}
onSearchChange={onChangeHandler}
onSearchButtonClick={onSearch}
selectedSearchOn={selectedSearchOn}
/>

{!!showingPost ? <BlogPost post={showingPost}/>: null }
</div>
);
}

export default BlogPosts;

2. Recap

We used create-react-app module to create first React project (Javascript and Typescript based). Then, we added first React component (Welcome.js and Welcome.tsx) in the projects. We started building a blog website which have functionality to list posts, search posts and show post. Then, We created BlogPosts.tsx which was only showing the name of posts. Then, we created two components BlogListing.tsx to show the list of posts and BlogPost.tsx to show the currently viewing post. Then, we added statement management in BlogPosts.tsx to show the post whenever post link is clicked in BlogListing.tsx component. Next, we added BlogSearch.tsx component to search blog based on Title or Tags.

3. What's next?

In the next post, we will introduce Redux to manage the state and reselect to add selector in the application. Stay tuned!.

Note: You can download the final source code for this application from Github.



Tags: React, React Hooks, ReactJS, Typescript

← Back home