How to create a Hacker News clone using React
Hacker News is a popular website among entrepreneurs and programmers. This site focuses on computer science and business.
Hacker News' simple layout may be suitable for some individuals. However, if you want a more engaging and personalized version, you can use the helpful API to create a customized Hacker News experience. Also, building Hacker News copy can help you strengthen your React skills.
Setting up the project and development server
Packages needed in programming include:
- React Router to handle routing in Single Page Application (SPA).
- HTMLReactParser to parse the HTML returned by the Application Programming Interface (API).
- MomentJS to handle the date returned by the API.
Open this terminal and run:
yarn create vite You can also use Node Package Manager (NPM) if you prefer it over yarn. The above command will use the Vite tool to build a basic project. Give the project a name and when prompted for the framework, select React and set the variable to JavaScript .
Now put the cd into the project directory and install the packages mentioned earlier by running the following command in the terminal:
yarn add html-react-parser yarn add react-router-dom yarn add moment yarn dev After installing all the packages and starting the server programming, open the project in any code editor and create 3 folders in the src named respectively: components , hooks , and pages .
In the components folder , add two files: Comments.jsx and Navbar.jsx . In the hooks folder , add a file useFetch.jsx . Then, in the pages folder , add two files ListPage.jsx and PostPage.jsx .
Delete the App.css file and replace the content of the main.jsx file with:
yarn add html-react-parser yarn add react-router-dom yarn add moment yarn dev In the App.jsx file , remove all boilerplate code and edit the file so that you have only functional components:
function App() { return ( <> ) } export default App Import the required modules:
import { Routes, Route } from 'react-router-dom' import ListPage from './pages/ListPage' import Navbar from './components/Navbar' import PostPage from './pages/PostPage' In the React fragment, add Routes components with 3 Route child components with paths: / , /:type , and /item/:id respectively.
}> }> }> Create custom hook useFetch
This project uses 2 APIs. The first API is responsible for fetching the list of articles in the provided directory, and the second is the Algolia API, which is responsible for fetching a specific article and its comments.
Open the file useFetch.jsx , define the hook as the default export choice and import the useState and useEffect hooks .
import { useState, useEffect } from "react"; export default function useFetch(type, id) { }
Define 3 state variables named: data , error and loading with their respective setup functions.
const [data, setData] = useState();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(true);
Then add the useEffect hook with the dependencies: id and type .
useEffect(() => { }, [id, type]) Next in the callback, add the fetchData() function to fetch data from the appropriate API. If the parameter passed is type , use the first API. If not, use the second API.
async function fetchData() { let response, url, parameter; if (type) { url = "https://node-hnapi.herokuapp.com/"; parameter = type.toLowerCase(); } else if (id) { url = "https://hn.algolia.com/api/v1/items/"; parameter = id.toLowerCase(); } try { response = await fetch(`${url}${parameter}`); } catch (error) { setError(true); } if (response) if (response.status !== 200) { setError(true); } else { let data = await response.json(); setLoading(false); setData(data); } } fetchData(); Finally, return the loading , error , data state variables as an object.
return { loading, error, data }; Show list of posts by queryed category
Whenever the user navigates to / or /:type , React renders the ListPage component . To implement this feature, first import the required modules:
import { useNavigate, useParams } from "react-router-dom"; import useFetch from "./hooks/useFetch"; Then define the function element, and then assign the dynamic parameter, type to the variable type . If there are no dynamic parameters available, set the type variable to news. Then call the useFetch hook .
export default function ListPage() { let { type } = useParams(); const navigate = useNavigate(); if (!type) type = "news"; const { loading, error, data } = useFetch(type, null); } Next, return the appropriate JSX code depending on whether one of the loading , error or data state variables is true .
if (error) { return Something went wrong! } if (loading) { return Loading } if (data) { document.title = type.toUpperCase(); return {type} {data.map(item => navigate(`/item/${item.id}`)}> {item.title} {item.domain && open(`${item.url}`)}> ({item.domain})} )} }
Create a PostPage Component
First, import the appropriate modules and components, then define the default function element, attach the dynamic parameter id to the variable id , call the useFetch hook . Make sure you unstructure the response.
import { Link, useParams } from "react-router-dom"; import parse from 'html-react-parser'; import moment from "moment"; import Comments from "./components/Comments"; import useFetch from "./hooks/useFetch"; export default function PostPage() { const { id } = useParams(); const { loading, error, data } = useFetch(null, id); } And like the ListPage component, JSX now matches based on the state of the following variables: loading , error and data .
if (error) { return Something went wrong! } if (loading) { return Loading } if (data) { document.title=data.title; return {data.title} {data.url && Visit Website} {data.author} {moment(data.created_at).fromNow()} {data.text && {parse(data.text)}} Comments } Show comment section with nested replies
Import the parse and moment modules . Defines the default function element Comments , takes the commentsData array as an attribute, traverses the arrays, and shows the Node component for each element.
import parse from 'html-react-parser'; import moment from "moment"; export default function Comments({ commentsData }) { return <> {commentsData.map(commentData => )} } Next, define the Node function element just below the Comments . The Node component now renders comments, metadata, and replies to each comment (if any) by recursively rendering itself.
function Node({ commentData }) { return { commentData.text && <> {commentData.author} {moment(commentData.created_at).fromNow()} {parse(commentData.text)} } {(commentData.children) && commentData.children.map(child => )} } In the above code block, parse is responsible for parsing the HTML stored in commentData.text , while moment is responsible for parsing the comment time and returning the associated time using the fromNow() method .
Create a Navbar . component
Open the Navbar.jsx file and import the NavLink module from react-router-dom . Finally, define the function element and return a parent nav with 5 NavLink elements pointing to the appropriate categories (or styles).
import { NavLink } from "react-router-dom" export default function Navbar() { return } Congratulations! You just built a front-end client for Hacker News.
Hope the article is useful to you!
You should read it
- React mistakes to avoid for successful app development
- How to build a CRUD to-do list app and manage its state in React
- How to use Sass in React
- How to create a swipeable interface in a React app using Swiper
- How to detect clicks outside a React component using a custom hook
- How to develop React apps that analyze emotions using OpenAI API