A pagination state management with useReducer
Tags: react, webdev
Published: 2022-02-12 06:29:07

This is my memo when I implemented a pagination feature with useReducer.

### What is useReducer and when to use it

According to [the official document](https://reactjs.org/docs/hooks-reference.html#usereducer), useReducer is an alternative to useState. It says that there are mainly two situations which useReducer is better than useState:
>  when you have complex state logic that involves multiple sub-values

and
>  when the next state depends on the previous one

I decided to use useReducer because it is necessary to simultaneously manage page numbers and an array whose elements are rendered in the page as well as other pagination related states.

### Basic Syntax

The basic syntax of useReducer is the below.

    

```javascript
const [state, dispatch] = useReducer(reducer, initialState)
```

It accepts a **(state, action) => newState **type reducer and returns a pair of current state and dispatch. This is syntax is quite similar to Redux.

### Step by step procedure with my actual code

This is the actual code.

**1️) Set initial state**
```javascript
    const initialState = {
      results: [],
      query: "",
      totalItems: 0,
      startIndex: 0,
      page: 1,
    };
```
**2) Define a reducer**
```javascript
    const searchReducer = (currntState, action) => {
      if (action.type === "UPDATE") {
        return {
          ...currntState,
          results: action.results,
          totalItems: action.totalItems,
        };
      }

    if (action.type === "QUERY") {
        return {
          ...currntState,
          query: action.query,
          startIndex: 0,
          page: 1,
        };
      }

    if (action.type === "PAGINATION") {
        return {
          ...currntState,
          page: action.page,
          startIndex: (action.page - 1) * 20 - 1,
        };
      }
    };
```
You can use switch statement instead of if statement.

**3) Call it inside of component function**
```javascript
    const [searchState, dispatchSearch] = useReducer(searchReducer, initialState);
```
**4) Call dispatch method with each type respectively**

**type:”PAGINATION”**
```javascript
    const pageChangeHandler = (event, newValue) => {
        dispatchSearch({ type: "PAGINATION", page: newValue });
      };
```
**type: “QUERY”**
```javascript
    const searchHandler = () => {
        setInit(false);
        sendGetRequest(searchInputRef.current.value);
        dispatchSearch({
          type: "QUERY",
          query: searchInputRef.current.value,
        });
        searchInputRef.current.value = "";
      };
```
**type:”UPDATE”**
```javascript
    useEffect(() => {
        if (loadedBooksData && !init) {
          dispatchSearch({
            type: "UPDATE",
            results: loadedBooksData.results,
            totalItems: loadedBooksData.totalItems,
          });
        }
      }, [loadedBooksData, init]);
```
Here is the whole code snippet of SearchBooksComponents.
```javascript
    import { Fragment, useRef, useReducer, useEffect, useState } from "react";
    import { Route, Routes } from "react-router-dom";
    import SearchIcon from "[@mui/icons-material](http://twitter.com/mui/icons-material)/Search";
    import Pagination from "[@mui/material](http://twitter.com/mui/material)/Pagination";
    import useHttp from "../hooks/use-http";
    import BookList from "./BookList";
    import LoadingSpinner from "../UI/LoadingSpinner";
    import ErrorMessage from "../UI/ErrorMessage";
    import BookDetail from "./BookDetail";

    import { getSearchBooks } from "../lib/api";
    import classes from "./SearchBooks.module.css";

    const initialState = {
      results: [],
      query: "",
      totalItems: 0,
      startIndex: 0,
      page: 1,
    };
    const searchReducer = (currntState, action) => {
      if (action.type === "UPDATE") {
        return {
          ...currntState,
          results: action.results,
          totalItems: action.totalItems,
        };
      }

    if (action.type === "QUERY") {
        return {
          ...currntState,
          query: action.query,
          startIndex: 0,
          page: 1,
        };
      }

    if (action.type === "PAGINATION") {
        return {
          ...currntState,
          page: action.page,
          startIndex: (action.page - 1) * 20 - 1,
        };
      }
    };

    const SearchBooks = () => {
      return (
        <Routes>
          <Route path="" element={<SearchBooksComponents />} />
          <Route path=":bookId/*" element={<BookDetail />} />
        </Routes>
      );
    };
    export default SearchBooks;

    const SearchBooksComponents = () => {
      const [init, setInit] = useState(true);
      const [searchState, dispatchSearch] = useReducer(searchReducer, initialState);
      const searchInputRef = useRef("");
      const {
        sendRequest: sendGetRequest,
        status,
        data: loadedBooksData,
        error,
      } = useHttp(getSearchBooks, true);

    const searchHandler = () => {
        setInit(false);
        sendGetRequest(searchInputRef.current.value);
        dispatchSearch({
          type: "QUERY",
          query: searchInputRef.current.value,
        });
        searchInputRef.current.value = "";
      };

    const pageChangeHandler = (event, newValue) => {
        dispatchSearch({ type: "PAGINATION", page: newValue });
      };

    useEffect(() => {
        if (searchState.query && !init) {
          sendGetRequest(searchState.query, searchState.startIndex);
        }
      }, [searchState.query, searchState.startIndex, sendGetRequest, init]);

    useEffect(() => {
        if (loadedBooksData && !init) {
          dispatchSearch({
            type: "UPDATE",
            results: loadedBooksData.results,
            totalItems: loadedBooksData.totalItems,
          });
        }
      }, [loadedBooksData, init]);
      return (
        <Fragment>
          <div className={classes.search}>
            <input type="text" placeholder="search books" ref={searchInputRef} />
            <button type="submit" onClick={searchHandler}>
              <SearchIcon sx={{ fontSize: 35 }} />
            </button>
          </div>
          {status === "loading" && !init && <LoadingSpinner />}
          {status === "completed" && error && <ErrorMessage />}
          {status === "completed" && !error && (
            <Fragment>
              <BookList results={searchState.results} />
              <div className={classes.pagination}>
                <Pagination
                  count={Math.ceil(searchState.totalItems / 20)}
                  page={searchState.page}
                  onChange={pageChangeHandler}
                />
              </div>
            </Fragment>
          )}
        </Fragment>
      );
    };
```

Thank you for reading :)
I'm happy if you give me some feedback!

The original article is [here](https://medium.com/@lada496/a-pagination-state-management-with-usereducer-176310c6edf)