React with hooks and Typescript

React with hooks and Typescript

<strong>This post expects you to have had minor exposure to Typescript. If this isn’t the case I would recommend that you firstly read an introductory post, such as [this one](</strong><a href="https://medium.freecodecamp.org/typescript-javascript-with-super-powers-a333b0fcabc9)." target="_blank"><strong>https://medium.freecodecamp.org/typescript-javascript-with-super-powers-a333b0fcabc9).</strong></a><strong> Not required, but certainly will help.</strong>

This post expects you to have had minor exposure to Typescript. If this isn’t the case I would recommend that you firstly read an introductory post, such as this one.](https://medium.freecodecamp.org/typescript-javascript-with-super-powers-a333b0fcabc9). "https://medium.freecodecamp.org/typescript-javascript-with-super-powers-a333b0fcabc9).")** Not required, but certainly will help.**

React hooks are officially a thing. Woot! I previously wrote about Easy Peasy, a global state library for React that leverages hooks and focuses on having a super simple but powerful API.

easy-peasy.js

import { StoreProvider, createStore, useStore, useActions } from 'easy-peasy';

// 👇 create your store, providing the model
const store = createStore({
  todos: {
    items: ['Install easy-peasy', 'Build app', 'Profit'],
    // 👇 define actions directly on your model
    add: (state, payload) => {
      // do simple mutation to update state, and we make it an immutable update
      state.items.push(payload)
      // (you can also return a new immutable instance if you prefer)
    }
  }
});

const App = () => (
  // 👇 wrap your app to expose the store
  <StoreProvider store={store}>
    <TodoList />
  </StoreProvider>
)

function TodoList() {
  // 👇  use hooks to get state or actions
  const todos = useStore(state => state.todos.items)
  const add = useActions(actions => actions.todos.add)
  return (
    <div>
      {todos.map((todo, idx) => <div key={idx}>{todo}</div>)}
      <AddTodo onAdd={add} />
    </div>
  )
}

This post is going to take things to a whole new level by showing you how you can compliment this API with the power and safety offered by Typescript.

I am going to take us step by step through building a naive todos application. Whilst this is completely unimaginative, I hope its boring familiarity will allow readers to more easily focus on the typing and APIs.

The code for this post exists in this GitHub repository.

Right, let’s get to it.

Set up

To get going as fast as possible we’ll leverage Create React App, along with its Typescript support.

Firstly, bootstrap an application, specifying that it support Typescript.

npx create-react-app type-safe-state-ftw --typescript

Ok, change directory into your newly created application.

cd type-safe-state-ftw

Then install Easy Peasy.

npm install easy-peasy

This single dependency includes everything you to support robust global state within your application. Some of its features includes:

  • Derived state
  • Remote data fetching/persisting
  • Auto memoization for performance
  • Simple mutation based API that converts to immutable updates
  • React Hooks to use with our components
  • Full Redux interoperability (including Redux Dev Tools)

And it only carries a very respectable 9kb gzip cost — all dependencies included.

Defining our Model

We will start by creating types to represent the model for our store.

model.ts

type TodosModel = {
  items: string[];
}

type NotificationModel = {
  msg: string;
}

export type StoreModel = {
  todos: TodosModel;
  notification: NotificationModel;
}

Fairly self explanatory. We will have some todos along with a notification msg.

Create our Store

Now we can use the StoreModel whilst creating our store.

store.ts

import { createStore } from 'easy-peasy';
import { StoreModel } from './model';

//                           👇
const store = createStore<StoreModel>({
  todos: {
    items: ["Install easy-peasy", "Build app", "profit"]
  },
  notification: {
    msg: ""
  }
});
  
export default store;

By providing our StoreModel as a type parameter on createStore we get two things: type checking that ensures our implementation matches the StoreModel type, and auto completion whilst we do so.

The returned store now has all the type information baked in, so any interaction with it will have Typescript helping us.

FYI — createStore returns a Redux Store, with all the Store APIs being available against it.### Grabbing state directly off the Store

Sweet, let’s try grab some state directly of the store.

getState.ts

store.getState().todos.items;
// ["Install easy-peasy", "Build app", "profit"]

This operation is fully typed.

Zing.

Defining actions to mutate state

Actions are responsible for updating the state on our model. Let’s expand our model types to indicate where we intend the actions to exist.

actions.ts

import { Action } from "easy-peasy";

type TodosModel = {
  items: string[];
  add: Action<TodosModel, string>; // 👈
}
    
type NotificationModel = {
  msg: string;
  set: Action<NotificationModel, string>; // 👈
}

export type StoreModel = {
  todos: TodosModel;
  notification: NotificationModel;
}

The Action type is a generic type, where its first type argument is the model that it is operating against, and the second type argument is the type of the payload it will receive.

action-def.ts

//         👇model to operate against
Action<TodosModel, string>;
//                   👆payload

It’s also completely fine for an Action to contain no payload.

Implementing actions to update state

After updating our StoreModel Typescript will be shouting at us that our store implementation does not meet its expectations.

Let’s implement the missing actions against our store.

actions-imp.ts

const store = createStore<StoreModel>({
  todos: {
    items: ["Install easy-peasy", "Build app", "profit"],
    // 👇
    add: (state, payload) => {
      state.items.push(payload);
    }
  },
  notification: {
    msg: "",
    // 👇
    set: (state, payload) => {
      state.msg = payload;
    }
  }
});

You can see the actions receive the state they relate to as well as any payload that was provided to them. We use the actions to update our state — under the hood these mutations are converted into an immutable update against our store.

FYI you can also return “new immutable state” within your actions if you prefer. I prefer the “mutable” form as it’s less error prone and more concise, but this is a matter of personal preference.

alt-action-form.ts

const store = createStore<StoreModel>({
  todos: {
    // ...
    add: (state, payload) => {
      return {
        ...state,
        items: [
          ...state.items,
          payload
        ]
      };
    }
  },
  // ...
});

We will get type checking and autocomplete whilst implementing the actions.

Firing actions directly via the store

Similar to accessing state directly against the store, we can also fire our actions via the store. They are bound to the store’s dispatchat the same path as they were defined within our model.

dispatch.ts

store.dispatch.todos.add("Finish reading this article");

And yep, typed again.

Expose the store to your application

Before we can use our store within our React components we need to wrap our application with the StoreProvider, providing the store instance.

app.tsx

import React from "react";
import { render } from "react-dom";
import { StoreProvider } from "easy-peasy";  // 👈
import store from "./store";

render(
  <StoreProvider store={store}>
    <App />  
  </StoreProvider>,
  document.querySelector("#root");
);

Almost there.

Getting our hooks ready

Easy Peasy ships with _hook_s to interact with the store within our React components. However, it will be great if we can ensure that the hooks contain all the type information about our store so that we don’t lose any of the type safety and autocomplete goodness we have gotten used to.

We can create pre-baked typed versions of our hooks via the [createTypedHooks]([https://github.com/ctrlplusb/easy-peasy#createtypedhooks)](https://github.com/ctrlplusb/easy-peasy#createtypedhooks) "https://github.com/ctrlplusb/easy-peasy#createtypedhooks)") helper.

hooks.ts

import { createTypedHooks } from "easy-peasy";
import { StoreModel } from "./model";

const { 
  useActions, 
  useStore, 
  useDispatch 
} = createTypedHooks<StoreModel>();
  
export { useActions, useDispatch, useStore };

Now whenever we need to use a hook from our hooks.ts file they will be able to guard and guide us via the type information we provided to them.

Hooking it all up

The moment we have been leading up to. Here’s how we use our state and actions within our components.

todos.tsx

import { useState } from "react";
import { useStore, useActions } from "./hooks"; // 👈

export default function Todos() {
  // 👇 pull out state from our store
  const items = useStore(state => state.todos.items);

  // 👇 pull out actions from our store
  const add = useActions(actions => actions.todos.add);

  const [newTodo, setNewTodo] = useState("");

  return (
    <div>
      <ul>
        {items.map((todo, idx) => 
           <li key={idx}>{todo}</li>
         )}
      </ul>
      <input 
        type="text"
        onChange={e => setNewTodo(e.target.value)}
        value={newTodo}
      />
      <button onClick={() => add(newTodo)}>Add</button>
    </div>
  );
}

This Todos component pulls out the todo items from the store, and subsequently renders them in a list. It allows us to add a new todo item. You will note that we not keeping our form state within our store. I highly recommend that transient state, such as form data, be created and used within the scope of your components themselves. In our example above we use the standard useState hook that React provides to help us with this.

This implementation is very naive, and could definitely do with some more work, but I’ve intentional kept it simple to make it quicker to read and understand what is going on.

As we consuming our typed hooks within our component we get all the type benefits.

This can be hugely helpful when refactoring your store as you will immediately get error messages in any component that is consuming refactored state — allowing you to quickly cycle through the instances and fix them.

What about side effects?

We can encapsulate operations such as network requests within “thunks”.

Let’s adjust the requirements for our store. We now need to ensure that any new todo items are sent to the backend via network request before adding it to the store.

We can use theThunk to describe an action that will allow us to encapsulate this behaviour.

thunk.ts

import { Action, Thunk } from 'easy-peasy';

type TodosModel = {
  items: string[];
  saved: Action<TodosModel, string>; 
  save: Thunk<TodosModel, string>; // 👈
}

We have done two things here. Firstly, we added the save thunk action, represented by the Thunk type. We then renamed the existing add action to saved. The thought process being is that we will first persist our new todo via the save thunk action, and subsequently add it to the store via the saved action. It will become clearer later on why we have a pair of actions for this.

Let’s update our store implementation accordingly.

thunk-model.ts

import { thunk } from "easy-peasy"; // 👈

const store = createStore<StoreModel>({
  todos: {
    items: ["Install easy-peasy", "Build app", "profit"],
    // 👇renamed
    saved: (state, payload) => {
      state.items.push(payload);
    },
    // 👇our thunk
    save: thunk(async (actions, payload) => {
      await todoService.save(payload);
      actions.saved(payload);
    })
  },
  // ...
});

As you can see the “thunk” action doesn’t receive the model state. Instead it receives the actions of the model it relates to. Therefore if we wish to update state we need to dispatch other “standard” actions to do so. This model is very much the same as found in popular libraries such as redux-thunk.

Within our save thunk action we make a call to our todoService, then we dispatch the saved action to ensure the todo is added to our store.

We can now update our component, ensuring that it calls the newly created save thunk action instead.

todos-thunk.tsx

export default function Todos() {
  const items = useStore(state => state.todos.items);

  //    👇 
  const save = useActions(actions => actions.todos.save);

  const [newTodo, setNewTodo] = useState("");

  return (
    <div>
      ...
      <button onClick={() => save(newTodo)}>Add</button>
    </div>
  );
}

What else?

There are many other features — which I will avoid going through right now out of fear of creating an overly long post. Some of the additional elements you can declare on your model include:

  • Derived state
  • Remote data fetching/persisting
  • Auto memoization for performance
  • Simple mutation based API that converts to immutable updates
  • React Hooks to use with our components
  • Full Redux interoperability (including Redux Dev Tools)

I encourage you to read the Easy Peasy GitHub page for more information about the library itself, view a more extensive example of the Typescript integration, and get detailed information in regards to the APIs.

In case you missed it, the code for this post lives in this GitHub repository.

Thanks for your time

I hope you enjoyed the read. Any and all feedback is greatly welcomed.

Feel free to log any issues via the GitHub repo.

Or take the following course:

Master ReactJS: Learn React JS from Scratch

Learn ReactJS: Code Like A Facebook Developer

ReactJS Course: Learn JavaScript Library Used by Facebook&IG

React: Learn ReactJS Fundamentals for Front-End Developers

React From The Ground Up

Why ReactJS is better for Web Application Development?

Why ReactJS is better for Web Application Development?

Web Application Development is the point of contact for a business in today's digital era. It is important to choose the right platform for Web Application Development to build a high end Web

Web Application Development is essential for a business in today’s digital era. Finding the right platform for Web Application Development is important for building an effective Web Application that can enhance the overall customer engagement. Here’s what makes ReactJS a better option for building your next Web Application.

ReactJS Development Services | ReactJS Development Company - Chapter 247 Infotech

ReactJS Development Services | ReactJS Development Company - Chapter 247 Infotech

Chapter 247 Infotech is a leading ReactJS development company in India, USA, offering ReactJS development services at par to a spectrum of business domains from E-commerce, healthcare to Edutech at

The ease of user interface in an application drives the customer engagement. By leveraging ReactJS Development you can build Web Applications that are user centric and deliver high end user experience to tap into better business opportunities...

Pagination in ReactJs

Pagination in ReactJs

There are a lot of resourceful materials online that give good insights into pagination in ReactJs, as well as NPM packages you can easily use

There are a lot of resourceful materials online that give good insights into pagination in ReactJs, as well as NPM packages you can easily use. As much as I appreciate those materials and love to use those packages, they mostly deal with loading the whole dataset on the page first then completely handle the pagination in the frontend. I am approaching this article with the concept of loading the exact data needed on the page, then manually loading other dataset based on the request when the user clicks the pagination number display. Below is the content structure to guide us through this article:

Table of Contents
  • Project Setup
  • HTML and CSS Styling
  • Pagination Data Format
  • Sample API request
  • Displaying the initial data
  • Showing Page Number and getting Other data
Project Setup

We are going to use create-react-app v0.1.0 which has the CSS Module configured already. Open your terminal and cd to the folder you want the project installed. Then run the below command:

npx create-react-app pagination  --use-npm

The above command will download the project into the folder calledpagination. You need to cd into the folder and run npm start. If everything goes well, you will have a page that looks like below:

HTML and CSS Styling

Open the project in your favorite code editor and locate the App.js file, We need to prepare our App.js to the look exactly like the way we want it by adding the HTML code and CSS style below:

Create a new file called App.module.css in the same directory where you have your App.js, then import it into your App.js using:

import styles from './App.module.css';

I want us to handle the display of the pagination number first, below is the style and HTML structure of what we are going to use.

  render() {
    

    return (
      <div className={styles.app}>
        
        <table className={styles.table}>
          <thead>
            <tr>
              <th>S/N</th>
              <th>First Name</th>
              <th>Last Name</th>
            </tr>
          </thead>
          <tbody>
              <tr>
                <td>1</td>
                <td>Abel</td>
                <td>Agoi</td>
              </tr>
              <tr>
                <td>2</td>
                <td>Muyiwa</td>
                <td>Aregbesola</td>
              </tr>
              <tr>
                <td>3</td>
                <td>Opeyemi</td>
                <td>Agoi</td>
              </tr>
              <tr>
                <td>4</td>
                <td>Ope</td>
                <td>Aina</td>
              </tr>
          </tbody>
        </table>


        <div className={styles.pagination}>
          <span>&laquo;</span>
          <span className={styles.active}>1</span>
          <span>2</span>
          <span>3</span>
          <span>4</span>
        </div>

      </div>
    );
  }

pagination_01.js

Add the content below into your App.module.css.

.app {
    width: 50%;
    margin: 0 auto;
}

table {
  border-collapse: collapse;
  border-spacing: 0; 
}


table {
  border-collapse: separate;
  border-spacing: 0;
  color: #4a4a4d;
  font: 14px/1.4 "Helvetica Neue", Helvetica, Arial, sans-serif;
  width: 100%;
}
tr {
  overflow-x: scroll;
}
th,
td {
  padding: 15px 15px;
  vertical-align: middle;
  /* text-align: left; */
}
thead {
  font-size: 14px;
  line-height: 24px;
  font-family: Lato;
  border: 1px solid transparent;

  max-width: 100%;
  font-weight: 900;
  line-height: 24px;
  mix-blend-mode: normal;

  color: rgba(51, 51, 51, .5);
  background: rgba(255, 255, 255, .9);
}
thead tr th {
  padding: 15px 15px;
  border: 1px solid transparent;


  text-align: left;
}
tbody {
  max-width: 100%;
}
tbody tr:nth-child(odd) {
  background: #f0f0f2;
}
tbody tr:hover {
  background: #f0f0f2;
}
td {
  padding: 15px 15px;
}
td:first-child {
}


.pagination {
    margin-top: 25px;
}
.pagination span {
  cursor: pointer;
  color: black;
  float: left;
  padding: 8px 16px;
  text-decoration: none;
  transition: background-color .3s;
  border: 1px solid #ddd;
}

.pagination span.active {
  background-color: #0099FF;
  color: white;
  border: 1px solid #0099FF;
}

pagination_app.module.css

Sorry for the plenty code written so far :), I want us to have a good looking table with pagination style in place before we move into the actual paging. If everything goes well, your view should look like below:

Pagination Data Format

In most cases, when you are making API calls to an endpoint that returns a paginated data, you need to pass at least the page number with the URL, hence a sample URL will look like below:

https://reqres.in/api/users?page=2

The most important thing to take note of in the URL above is the page=2 where 2 is the page number dataset we want to get. It can be 3,4 or any number as much as the dataset we have in the backend.

The response will always contain three important data which are per_page, total and the actual data we want to loop through. A sample response looks like below:

Sample API request

Talking about making an API request to the backend, We need a backend to make the request to, I decide to use https://reqres.in/ as the API endpoint for this tutorial because it is free, always available and reliable. You can decide to make your API request directly inside your component’s ComponentDidMount() or dispatch an action to redux from your ComponentDidMount() but for the purpose of this tutorial, we are going to make the API call from the App.js componentDidMount().

Firstly, we need to set the component’s state like below inside your App.js

  state = {
    users: null,
    total: null,
    per_page: null,
    current_page: null
  }

pagination_component_state.js

users is going to be the data we are going to loop over, while total and per_page is going to help us with calculating paging logic while the current_page will be used to style the active pagination link.

The next thing we should do is create a helper method that will serve the purpose of making an HTTP request to the API endpoint and also update the state with the response data. The method will look like below:

  makeHttpRequestWithPage = async pageNumber => {
    let response = await fetch(`https://reqres.in/api/users?page=${pageNumber}`, {
      method: 'GET',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
    });

    const data = await response.json();

    this.setState({
      users: data.data,
      total: data.total,
      per_page: data.per_page,
      current_page: data.page,
    });
  }

pagination_http_request.js

This method will accept a parameter called pageNumber so it can be reusable and will always update the state with the right data when the response is successful.

Since on page load, we need to make the HTTP request to the backend, and we are going to do this inside thecomponentDidMount() by calling the method above and passing it the first-page number we want which should be 1. Hence, the componentDidMount() will look like below:

 componentDidMount() {
    this.makeHttpRequestWithPage(1);
  }

pagination_componentDidMount.js

If we add console.dir(this.state.users) inside the render() method, below will be printed in the console

The null was before the data arrived, once the data arrived, it updates the state, hence the array of users data.

Displaying the initial data

Haven gotten the data needed, we need to loop through the data and display it. Hence we can update our render method to have below:

    let users;

    if (this.state.users !== null) {
      users = this.state.users.map(user => (
        <tr key={user.id}>
          <td>{user.id}</td>
          <td>{user.first_name}</td>
          <td>{user.last_name}</td>
        </tr>
      )); 
    }
    
    return (
      <div className={styles.app}>
        
        <table className={styles.table}>
          <thead>
            <tr>
              <th>S/N</th>
              <th>First Name</th>
              <th>Last Name</th>
            </tr>
          </thead>
          <tbody>
              { users }
          </tbody>
        </table>


        <div className={styles.pagination}>
          <span>&laquo;</span>
          <span className={styles.active}>1</span>
          <span>2</span>
          <span>3</span>
          <span>4</span>
          <span>&raquo;</span>
        </div>

      </div>
    );

gistfile1.txt

I replaced the dummy data we had inside the with the result of the loop which I equated to users. We have the assurance that when the state changes, ReactJs will automatically update the content of the table. The final stage is displaying the page logic and getting the other contents based on the page number clicked which will be sent to the API endpoint.

Showing Page Number and getting other data

Before we talk about showing page number automatically using the desired logic, I want us to manually show those numbers and make the actual API calls when the numbers are clicked. For now, we are going to hard code the pagination numbers ourselves like below:

<div className={styles.pagination}>
  <span onClick={() => this.makeHttpRequestWithPage(1)}>1</span>
  <span onClick={() => this.makeHttpRequestWithPage(2)}>2</span>
  <span onClick={() => this.makeHttpRequestWithPage(3)}>3</span>
  <span onClick={() => this.makeHttpRequestWithPage(4)}>4</span>
</div>

pagination_hard_code.js

The above code will look like below when previewed in the browser.

Notice that each span has an event handler attached to it, and I passed the page number to that event handler, so anytime we click on the pagination link, it will make a new HTTP request and update the component states, hence the user’s table data. We do not want to hard-code the links as we did above, so we need to automatically display those links.

So we’re planning on showing the page numbers for a series of pieces of data so that users can easily navigate multiple items. There are a few things that we need to know first:

  • The page that we’re on
  • Total number of items
  • Number of items per page

Good news is that we have captured all these things in our component’s state.

Next, we need to look at how we want to display the page numbers, there is a wide range of methods that people use:

  • Simple Next/Previous buttons with no numbers
  • A list of all possible pages
  • Page 1 & the last page, with the current page (and 2 above/below) shown

I personally prefer to show the very first page, that last page, and then the current page with 2 pages above & below. So for example on page 12 out of 24 pages we’d see:

1, 10, 11, 12, 13, 14, 24

This allows users to quickly navigate to the start, and to the end, as well as jump through multiple pages at once. For the purpose of this tutorial, I am going to show us how to show a list of all possible pages(item two above) then item three too.

The Arithmetic

We need to work out the total number of pages, for this, we want to take the total number of items that there are, and divide it by the number of items per page. But we want to make sure that we take that number and round it up.

So if there were 12 items in total, and we were showing 5 per page, we’d have a total of 3 pages of items. If we were to show 3 per page, we’d show 4 pages.

const pageNumbers = [];
for (let i = 1; i <= Math.ceil(this.state.meta.total / this.state.meta.per_page); i++) {
    pageNumbers.push(i);
}

page_logic_pagination.js

Haven gotten the page numbers, we need to loop through to display the span since we want to show all possible numbers first, our loop will look like below:

renderPageNumbers = pageNumbers.map(number => {
  let classes = this.state.current_page === number ? styles.active : '';

  return (
    <span key={number} className={classes} onClick={() => this.makeHttpRequestWithPage(number)}>{number}</span>
  );
});

pagination_all_numbers_loop.js

We need to update our pagination view to look like below:

<div className={styles.pagination}>
  <span onClick={() => this.makeHttpRequestWithPage(1)}>&laquo;</span>
  {renderPageNumbers}
</div>

pagination_view._01js

Congrats, we have successfully handle pagination, make HTTP request to the backend and changing the table content when user click on the page number to see.

To be sure we are on the same page, my App.js code looks like below:

import React, { Component } from 'react';
import styles from './App.module.css';

class App extends Component {


  state = {
    users: null,
    total: null,
    per_page: null,
    current_page: 1
  }


  componentDidMount() {
    this.makeHttpRequestWithPage(1);
  }


  makeHttpRequestWithPage = async pageNumber => {
    const response = await fetch(`https://reqres.in/api/users?page=${pageNumber}`, {
      method: 'GET',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
    });

    const data = await response.json();

    this.setState({
      users: data.data,
      total: data.total,
      per_page: data.per_page,
      current_page: data.page
    });
  }


  render() {

    let users, renderPageNumbers;

    if (this.state.users !== null) {
      users = this.state.users.map(user => (
        <tr key={user.id}>
          <td>{user.id}</td>
          <td>{user.first_name}</td>
          <td>{user.last_name}</td>
        </tr>
      ));
    }

    const pageNumbers = [];
    if (this.state.total !== null) {
      for (let i = 1; i <= Math.ceil(this.state.total / this.state.per_page); i++) {
        pageNumbers.push(i);
      }


      renderPageNumbers = pageNumbers.map(number => {
        let classes = this.state.current_page === number ? styles.active : '';

        return (
          <span key={number} className={classes} onClick={() => this.makeHttpRequestWithPage(number)}>{number}</span>
        );
      });
    }

    return (


      <div className={styles.app}>

        <table className={styles.table}>
          <thead>
            <tr>
              <th>S/N</th>
              <th>First Name</th>
              <th>Last Name</th>
            </tr>
          </thead>
          <tbody>
            {users}
          </tbody>
        </table>


        <div className={styles.pagination}>
          <span onClick={() => this.makeHttpRequestWithPage(1)}>&laquo;</span>
          {renderPageNumbers}
          <span onClick={() => this.makeHttpRequestWithPage(1)}>&raquo;</span>
        </div>

      </div>
    );
  }

}

export default App;

pagination_app.js

and my view like below:

We can change the page number display logic to below since it will accommodate for large dataset.

renderPageNumbers = pageNumbers.map(number => {
  let classes = this.state.current_page === number ? styles.active : '';

  if (number == 1 || number == this.state.total || (number >= this.state.current_page - 2 && number <= this.state.current_page + 2)) {
    return (
      <span key={number} className={classes} onClick={() => this.makeHttpRequestWithPage(number)}>{number}</span>
    );
  }
});

pagination_another_display_logic.js

Thanks for reading.