A ReactJS Clone of Dev.to: Working with React Hooks

maswerdna

Samson Andrew

Posted on July 27, 2021

A ReactJS Clone of Dev.to: Working with React Hooks

Link to GitHub repo is now available. Check it out at the bottom of this article.
Check the Live Site hosted on GitHub Pages.

Recently, I needed to prepare a lecture material on Consuming REST APIs in your React Project so I decided to write about the dev.to API - available at https://docs.forem.com/api/. Below is what I put together while working on the project.

Introduction

An API, or application programming interface, is a set of rules that define how applications or devices can connect to and communicate with each other. A REST API is an API that conforms to the design principles of the REST, or representational state transfer architectural style. What is a REST API? | IBM.

React is a Facebook owned, Opensource JavaScript library used to develop responsive and lightweight UIs.

Dev.to is a developer's blogging website described as a constructive and inclusive social network for software developers.

What you should know

To be able to understand this tutorial, you should have basic knowledge of JavaScript and React.

What you will learn in this part

1. Creating a new react app using npm or yarn

This section is added as a refresher

Before you can start building react apps you need to install the latest version of node on your development machine. npm and npx is bundled with the node installer. Download Node.js from the official website - Download | Node.js

After installation and setting up of your environment, you should be able to run the following command from your command line (CLI) - npx create-react-app my-awesome-app. Refer to the React official documentation or Create React App website for detailed instructions.

To learn about yarn, refer to yarn official documentation.

Now that you have created your app, it's time to cd my-awesome-app. Good! You are now in your app directory.

2. Working with React Hooks

Navigate to your project folder and open the src directory, i.e. C:/path/to/my-awesome-app/src, and open the index.js file with your favourite editor. I use either SublimeText or VSCode.

Your html index file can be found at C:/path/to/my-awesome-app/public/index.html. We will need this file later when we are ready to push to GitHub pages.

If you open the html file in a browser, you would be greeted with a blank page. So, to start your app, run the following command: npm start or yarn start and wait for the development server to start your app in your default browser.

Your CLI commands so far would look like the following

    > npx create-react-app my-awesome-app
    > cd my-awesome-app
    > npm start
Enter fullscreen mode Exit fullscreen mode

Once the server comes up, you would see the default React app loaded. It's now time to start building your own project by editing the index.js file that we opened earlier. Leave other files in the src directory for now. We will delete the ones we don't need later.

Delete the whole content of the index.js file and type the following:

file: index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(
  <React.StrictMode>{/* This component will notify us of potential problems */}
      <App />
  </React.StrictMode>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

Before we work on our App.js file, I would like us to create some components, so let's start with the navigation bar. You should access the dev.to homepage on a laptop to be able to see the navigation bar.

The Navbar view at >= 640px
dev-navbar
The Navbar view at < 640px
dev-nav-mobile

file: Navbar.js

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

function Navbar({query, onChange}) {
    return (
        <header className="flex header justify-between items-center p-2 px-3 m:p-0 m:px-0 m:pb-2">
            <h1 className="crayons-subtitle-2">Posts</h1>

            <nav className="crayons-tabs hidden s:flex" aria-label="View posts by">
                <ul className="crayons-tabs__list">
                    <li>
                        <Link data-text="Feed" to="/" className={"crayons-tabs__item" + (query === "/" ? ' crayons-tabs__item--current' : '')}>Feed</Link>
                    </li>
                    <li>
                        <Link data-text="Week" to="/top/week" className={"crayons-tabs__item" + (query === "/top/week" ? ' crayons-tabs__item--current' : '')}>Week</Link>
                    </li>
                    <li>
                        <Link data-text="Month" to="/top/month" className={"crayons-tabs__item" + (query === "/top/month" ? ' crayons-tabs__item--current' : '')}>Month</Link>
                    </li>
                    <li>
                        <Link data-text="Year" to="/top/year" className={"crayons-tabs__item" + (query === "/top/year" ? ' crayons-tabs__item--current' : '')}>Year</Link>
                    </li>
                    <li>
                    <Link data-text="Infinity" to="/top/infinity" className={"crayons-tabs__item" + (query === "/top/infinity" ? ' crayons-tabs__item--current' : '')}>Infinity</Link>
                    </li>
                    <li>
                        <Link data-text="Latest" to="/latest" className={"crayons-tabs__item" + (query === "/latest" ? ' crayons-tabs__item--current' : '')}>Latest</Link>
                    </li>
                </ul>
            </nav>

            <select className="right s:hidden f-16 pd5 b-r5 sh-focus" value={query} onChange={onChange}>
                <option value="/">Feed</option>
                <option value="/top/week">Week</option>
                <option value="/top/month">Month</option>
                <option value="/top/year">Year</option>
                <option value="/top/infinity">Infinity</option>
                <option value="/latest">Latest</option>
            </select>
        </header>
    )
}

export default Navbar;
Enter fullscreen mode Exit fullscreen mode

The Navbar component accepts 2 props - query and onChange. The query prop holds the current value of the category of articles that was fetched. There are 6 categories - feed, week, month, year, infinity and latest.

The onChange prop points to the callback that runs each time we change the article category with the select element.

Note that the Navbar component contains 2 functional elements, the nav and the select. Both elements are used together wherever they appear on the website and they both act on the same information which is the current article category, hence, there's no need to extract them into separate components.

Styling the Navbar component
For brevity sake, I would skip all CSS codes in this article, except when it provides any functionality. The full code can be found in the project repo on GitHub.

Responsiveness
Dev.to has 4 break-points, viz:

  1. 0 - 639 [Small to Medium Mobile devices]
  2. 640 - 767 [Large Mobile devices]
  3. 768 - 1023 [Tablet devices]
  4. 1024 - 1280 and up [Laptops]

The design of Dev.to follows the Mobile-first approach and the break-point rules can be declared as shown in the codes below:

*, *:before, *:after {
    /* Your general styles here */
    /* Styles for extra small devices */
}

@media screen and (min-width: 640px) {
    /* Takes care of small to medium devices */
}

@media screen and (min-width: 768px) {
    /* Takes care of tablet devices */
}

@media screen and (min-width: 1024px) {
    /* Takes care of laptop devices */
}
Enter fullscreen mode Exit fullscreen mode

Navbar Functionality
We have used the Link component from react-router-dom to handle our links. Please do not forget to npm install react-router-dom. We will see why this is necessary in a moment. We have also added an onChange event listener to the <select> element to handle the action.

Now, let's write the Navbar controller. We would add this controller in our App.js file.

file: App.js

import React, {useState, useEffect} from 'react';
import {Route, Switch, useHistory, useLocation} from 'react-router-dom';
// import Home from './Home';
// import Article from './Article';
// import Search from './Search';

function App() {
  const location = useLocation();// provided by the router bundle
  const history = useHistory();// provided by the router bundle

  const [posts, setPosts] = useState([]);
  const [failure, setFailure] = useState(false);
  const [query, setQuery] = useState(location.pathname);
  const [isShowing, setIsShowing] = useState(false);// for the Hamburger
  //
  function handleChange(event) {
    history.push(event.target.value); // adds a new entry to the history object
    // event.target.value could be one of "/, /top/week, /top/month, /top/year, /top/infinity, /latest"
  }
  function SideNavToggler() {// Hamburger Menu is the Side Navigator
    setIsShowing(isShowing => !isShowing);
  } // we use this state to decide whether the side menu should be revealed or hidden during the next click of the trigger element.
            //
  useEffect(() => {
    // 1. to avoid creating new object each time the component re-renders, we have to define this within the useEffect.
    // 2. if it was passed in a dependency, React will create new object each time, causing the effect hook to run repeatedly for every effect.
    // 3a. We are transforming the location pathname to something that the dev.to API understands, but we need to keep the path name SEO friendly.
    // 3b. dev.to/api/articles?top=7 gets us the articles created over the week, but we want to make it appear as https://dev-to-blog/top/week => https://dev.to/top/week - hence, the need for this mapping.
    const mymap = {
      '/': 0,
      '/top/week': 7,
      '/top/month': 30,
      '/top/year': 365,
      '/top/infinity': 366,
      '/latest': 1
    }
    const qpath = mymap[location.pathname]; // returns 0 for / and 7 for week...
    const fetchArticles = async () => {
      try {
        setFailure(false);
        setPosts([]);
        //
        const url = 'https://dev.to/api/articles' + (qpath ? '?top=' + qpath : '');
        const api_response = await fetch(url);
        const data = await api_response.json();

        if (api_response.status !== 200) {
          throw Error(api_response.error);
        }
        // console.log(data);
        setQuery(location.pathname); // update this after a successful API request
        setPosts(data);
      } catch (error) {
        // console.log(error);
        setFailure(true);
        setQuery(''); // do this to allow new request without change in location
      }
    }
    !isNaN(qpath) && fetchArticles();
  }, [location]) // the effect hook will only run when there is a change in the location's pathname, or after a failed request

  const navState = {SideNavToggler, isShowing};
  const data = {query, failure, posts, handleChange, ...navState};

  return (
    <div className="App">
      {/* <Switch>
        <Route path="/" render={() => <Home data={data} />} exact />
        <Route path="/top" render={() => <Home data={data} />} />
        <Route path="/latest" render={() => <Home data={data} />} />
        <Route path="/search" component={Search} />

        <Route render={() => <Article data={navState} />} />
      </Switch> */}
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

With this controller, if a user clicks on the nav link, the location will be updated, and since we have added the location as a dependency to the useEffet hook, we are confident that a new request would be made to the API backend and the UI will re-render upon successful request.

If you wish to learn more about the useState hook, you can read my article on that where I demonstrated the use of useState and useReducer hooks.

Summary

In this first 2 sections of this part, we have learnt how to create a new react app and how to use the useEffect hook to make asynchronous requests to the server. We also learnt how to use the useState hook to manage the internal state of our app.

We saw how we can update the browser history to activate a server request by using the React Router package with the useEffect hook and we also looked at using media queries to set break-points in our app for responsive design.

What's next?

In Part 2 of this article, we will take a dive into the world of React Router for SPA navigation and how to configure our app to be compliant with github pages mode of navigation.

If you like this article and would love to be notified when the next update is ready, you can add it to your reading list by clicking the Save button or you can as well follow my account.

Thank you ;)

Link to Source Code on GitHub

💖 💪 🙅 🚩
maswerdna
Samson Andrew

Posted on July 27, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related