Handling pagination with UseReducer

Handling pagination with UseReducer

As I worked on a website project's pagination area, I discovered that I had multiple states and user-defined functions. I decided to look for an alternative because it appeared unkempt and disorganized. One hook stood out to me from my research: useReducer.

I enjoy explaining things in terms of their purposes. Consider the reasoning behind a pagination feature, which allows users to change the number of rows displayed on a page, filter the table using a search query, and then reset the table to its initial state.

Up to 3-5 usestate variables and at least 4 functionalities would be included. If done right, this would work perfectly; however, it would mess up the coding. UseReducer is required when a project feature has several states and complex logic. The useReducer hook will handle that precisely because of this.

So let's get started.

Project Setup

I'm assuming you are familiar with the useState hook and React.

Open your terminal and run the commands one after the other

git clone https://github.com/victorgbonna/hashnode

cd .\hashnode\

npm i

git fetch origin

git checkout "day0.5-useReducerPagination"

npm run dev

A table of data from the JSON placeholder API is displayed. A filter box and pagination are also available. The /src/pages/index.js has the states and functionality. To make the programs easier to read, we'll utilize useReducer.

Applying useReducer

Let us comment out all the states and functions. So replace what is in your /src/pages/index.js file with this

import axios from "axios";
import { useEffect, useReducer, useState} from "react";

export default function Maintable() {  
  const [showFilter, setShowFilter]= useState(false)
  // const [users, setUsers]= useState(null)
  // const [filteredUsers, setFilteredUsers]= useState(null)
  // const [initialTotal, setInitialTotal]= useState(0)
  // const [total, setTotal]= useState(0)
  // const [query, setQuery]= useState({
  //   take:10, skip:0
  // })

  // const initialFilter=  {
  //   userId:"",id:"", title:""
  // }
  // const [filter, setFilter]= useState(initialFilter)

  useEffect(() => {
    const userFetched=async()=>{
      const response= await axios.get('https://jsonplaceholder.typicode.com/posts')
      // setUsers(response.data)
      // setFilteredUsers(response.data)
      // setTotal(response.data.length)
      // setInitialTotal(response.data.length)
    }
    userFetched()
  }, []);

  // const queryChanged=({queryName,queryValue})=>{
  //   setQuery({
  //     ...query, [queryName]:queryValue
  //   })
  // }
  // const filterApplied=(data)=>{
  //   let usersCopy=users
  //   let new_filterObj= data
  //   for (const key in new_filterObj) {
  //     const element = new_filterObj[key];
  //     usersCopy=usersCopy.filter(user=>user[key]===element)
  //   }
  //   setTotal(usersCopy.length)
  //   setFilteredUsers(usersCopy)
  //   setQuery({
  //     ...query, skip:0
  //   })
  //   setFilter({
  //     ...initialFilter, ...new_filterObj
  //   })
  //   return
  // }

  // const reset =()=>{
  //   setTotal(initialTotal)
  //   setFilteredUsers(users)
  //   setQuery({
  //     take:10, skip:0
  //   })
  //   setFilter(initialFilter)
  //   return 
  // }

  if(!users) return 
  return (
    <div className="maintableContainer">
      <Table setShowFilter={()=>setShowFilter(!showFilter)} 
        users={filteredUsers.slice(query.skip,query.take+query.skip)}/>

      <Paginator onPageClick={(
        {queryName, queryValue})=>
          null
        )
      } currentPageIndex={query.skip/query.take} take={query.take}
        total={total}
      />
      {showFilter && <FilterForm
        exFilterData={filter} 
        hideForm={()=>null}
        reset={()=>null}
        addFilter={(data)=>null}
      />}
    </div>
  );
}


function Table({users, setShowFilter}) {

  const cols=[
    "UserId", "Id", "Title", "Body"
  ]
  return (
    <div>
      <button onClick={setShowFilter}>
        Filter/Hide Box
      </button>
      <table className="table">
      <thead>
          <tr>
          {cols.map((col, index) => (
              <th key={index}>
                <div style={{display:"flex", columnGap:"3px"}}>
                  <p>{col}</p> 
                </div>

              </th>
          ))}
          </tr>
      </thead> 
      <tbody>
          {users.map((user={}, key) => {
          return (
            <tr key={key} style={{position:"relative"}}>
            <td>{user.userId}</td>

            <td>{user.id}</td>
            <td>{user.title.slice(0,30)+' ...'}</td>
            <td>{user.body.slice(0,30)+' ...'}</td>

          </tr>
          );
          })}
      </tbody>
      </table>
    </div>
  );
}
function Paginator({take,total, onPageClick, currentPageIndex}) {
  const rowChoice=Array.from({length: 100}, (_, i) => i+1);
  const pageNums= Array.from({length: Math.ceil(total/take)}, (_, i) => i+1);
  return (
  <div className="pagdiv">
      <div className='flex'>
          <p>showing</p>
          <Select value={take} options={rowChoice} onChange={(take)=>onPageClick({queryName:"take", queryValue:take})}/>
          <p>out of 100</p>
      </div>
      <div className='flex' style={{wordWrap:"break-all", maxWidth:"200px",flexWrap:"wrap"}}>
          {pageNums.map((pInd,ind)=>             
              <p key={ind} style={{cursor:"pointer"}} onClick={pInd===currentPageIndex+1?null:()=>onPageClick({queryName:"skip", queryValue:(pInd-1)*take})}
                className={pInd===currentPageIndex+1?'currentpagp':'pagp'}>{pInd}</p>                        
          )}
      </div>       
  </div>
  )
}



function FilterForm({hideForm, addFilter, reset, exFilterData}) {
  const [filterForm, setFilterForm]= useState({...exFilterData})
  const filterAction=()=>{
    let dataToBeFiltered={}
    const {userId, id, title}= filterForm
    console.log({userId, id ,title})
    for (const key in filterForm) {
      if(filterForm[key]){
        if(key=="userId" || key =="id"){
          dataToBeFiltered[key]=parseInt(filterForm[key])
        }
        else{
          dataToBeFiltered[key]=filterForm[key]
        }
      }            
    }
    console.log({dataToBeFiltered})

    if (Object.keys(dataToBeFiltered).length){
        addFilter(dataToBeFiltered)
        // hideForm()
    }
  }
  const resetAction=()=>{
    setFilterForm({
        ...initialFilter
    })
    reset()
    hideForm()
  }
return (
<div className='filterForm'>
  <div>
      <p>UserId</p>
      <input type="number" onChange={(e)=>setFilterForm({
          ...filterForm, userId:e.target.value
      })} value={filterForm.userId} placeholder="userId"/>
  </div>
  <div>
      <p>Id</p>
      <input type="number" placeholder='id' onChange={(e)=>setFilterForm({
          ...filterForm, id:e.target.value
      })} value={filterForm.id}/>
  </div>
  <div>
      <p>Title</p>
      <input type="text" name='title' onChange={(e)=>setFilterForm({
          ...filterForm, title:e.target.value
      })} value={filterForm.title} placeholder="title"/>
  </div>

  <div className='btnsection'>
      <button className='reset' onClick={resetAction}>Reset</button>
      <button className='filter' onClick={filterAction}>Filter</button>
  </div>
</div>
);
}
function Select({options, onChange, value,
}) {
  const [show,toggle]= useState(false)

  return (   
  <div style={{position:"relative"}} onClick={() => toggle(!show)}>
  <div className='optInp'>
      <p className='labelClass'>{value}</p>
      <div className='svg'>
          {show ? (
          <svg
              width="12"
              height="8"
              viewBox="0 0 14 8"
              fill="none"
              xmlns="http://www.w3.org/2000/svg"
          >
              <path
              d="M6.91626 2.36902L12.1453 7.56091C12.3313 7.74582 12.5829 7.84961 12.8452 7.84961C13.1075 7.84961 13.3592 7.74582 13.5453 7.56091C13.6369 7.47016 13.7096 7.3622 13.7593 7.24316C13.8089 7.12413 13.8345 6.9964 13.8345 6.86743C13.8345 6.73846 13.8089 6.61073 13.7593 6.4917C13.7096 6.37266 13.6369 6.2647 13.5453 6.17395L7.61719 0.28894C7.43114 0.104033 7.17954 0.000244141 6.91724 0.000244141C6.65493 0.000244141 6.40333 0.104033 6.21729 0.28894L0.289185 6.17395C0.197543 6.2647 0.124842 6.37266 0.0751953 6.4917C0.0255486 6.61073 0 6.73846 0 6.86743C0 6.9964 0.0255486 7.12413 0.0751953 7.24316C0.124842 7.3622 0.197543 7.47016 0.289185 7.56091C0.475358 7.74558 0.727033 7.84924 0.989258 7.84924C1.25148 7.84924 1.50304 7.74558 1.68921 7.56091L6.91626 2.36902Z"
              fill="#809FB8"
              />
          </svg>
          ) : (
          <svg
              width="12"
              height="8"
              viewBox="0 0 14 8"
              fill="none"
              xmlns="http://www.w3.org/2000/svg"
          >
              <path
              d="M6.91626 5.48059L12.1453 0.288696C12.3313 0.103789 12.5829 0 12.8452 0C13.1075 0 13.3592 0.103789 13.5453 0.288696C13.6369 0.379447 13.7096 0.487411 13.7593 0.606445C13.8089 0.72548 13.8345 0.853205 13.8345 0.982178C13.8345 1.11115 13.8089 1.23888 13.7593 1.35791C13.7096 1.47694 13.6369 1.58491 13.5453 1.67566L7.61719 7.56067C7.43114 7.74558 7.17954 7.84937 6.91724 7.84937C6.65493 7.84937 6.40333 7.74558 6.21729 7.56067L0.289185 1.67566C0.197543 1.58491 0.124842 1.47694 0.0751953 1.35791C0.0255486 1.23888 0 1.11115 0 0.982178C0 0.853205 0.0255486 0.72548 0.0751953 0.606445C0.124842 0.487411 0.197543 0.379447 0.289185 0.288696C0.475358 0.104031 0.727033 0.000366211 0.989258 0.000366211C1.25148 0.000366211 1.50304 0.104031 1.68921 0.288696L6.91626 5.48059Z"
              fill="#809FB8"
              />
          </svg>
          )}
      </div>        
  </div>

    {show && (
      <div className='listcontainer'>
      <ul>
        {options.map((option,index)=>
          <li key={index} 
          style={{color: '#666666'}}
          onClick={()=>onChange(option)}
          className='formopt'>{option}</li>           
        )}
      </ul>
      </div>
    )}
  </div>
)}

Create a "reducer" folder. Create a "paginatorReducer.js" file in the "reducer" folder.

Let us create the initial state of the states in the "paginatorReducer.js"

export const INITIAL_STATE = {
    filteredUsers:[],
    users:[],
    skip:0,
    take: 10,
    total:1,
    initialTotal:1,
    filter:{
        org:"",
        userName:"",
        email:"",
        date:"",
        phone:"",
        status:""
    }
  };

export const paginatorReducer = (state, action) => {
   //functions goes in here   
};

Terminologies

  • filteredUsers - array containing the filtered users.
  • users - array containing the initial users. This stores all the users while the "filteredUsers" changes.
  • skip - tells how many items to skip to the desired page number.
  • take - tells how many items to take per page.
  • total - tells how many items are in the "filteredUsers". Same as filteredUsers.length.
  • initialTotal- tells how many items are in the original "users". Same as users.length. This stores the users total while the "total" changes.
  • filter - this holds the filter query states.

import useReducer and "paginatorReducer" into the "src/pages/index.jsx" file.

import { INITIAL_STATE, paginatorReducer } from "../reducer/paginatorReducer";
  1. Initialize the useReducer

// Syntax: //const [state, dispatch] = useReduce(theReducerFunction, initialStateValue);

Terminologies

  • state - this holds the current state value.
  • dispatch - this is a function that updates the current state.
  • theReducerFunction - this is the reducer function that takes a state and action to return an updated state.
  • initialStateValue - this holds the initial state value.

In our case, it will be like this :

const [state, dispatch] = useReducer(paginatorReducer, INITIAL_STATE);
  1. Replace instances of usestate values with their corresponding reducer state values
export default function Maintable() {  
   // ...rest of the codes
  if(!state.users) return null //changes here 
  return (
    <div className="maintableContainer">
      {/* changes here */}
      <Table setShowFilter={()=>setShowFilter(!showFilter)} 
        users={state.filteredUsers.slice(state.skip,state.take+state.skip)}/>

      {/* changes here */}
      <Paginator onPageClick={(
        {queryName, queryValue})=>
          null
      } currentPageIndex={state.skip/state.take} take={state.take}
        total={state.total}
      />
      {showFilter && <FilterForm
        exFilterData={filter} 
        hideForm={()=>null}
        reset={()=>null}
        addFilter={(data)=>null}
      />}
    </div>
    )
}

So the INITIAL_STATE will hold the default and current state of the values, until a "dispatch" is invoked

Here the fun begins

USERS_FETCHED

We have to update the "users", "filteredUsers", "total" and "initialTotal" as soon as the API has been fetched. Add to the code block containing the "useEffect" hook.

 useEffect(() => {
    const userFetched=async()=>{
      const response= await axios.get('https://jsonplaceholder.typicode.com/posts')
      return dispatch({type:'USERS_FETCHED', payload:response.data})
    }
    userFetched()
  }, []);

The useEffect hook is used to return a function when there is a response from the API hook. The "dispatch" action passes an object argument with a type and payload property. Go over to the "src/reducer/paginatorReducer.js" .

this

...
export const paginatorReducer = (state, action) => {
   //functions goes in here   
};

changes to

...
export const paginatorReducer = (state, action) => {
   switch (action.type) {
       case "USERS_FETCHED":
        return {
          ...state,
          total:action.payload.length,
          initialTotal:action.payload.length,
          filteredUsers:action.payload,
          users:action.payload
        }; 
        default:  return state;
      }
};

Mind you that "USERS_FETCHED" was the action type property; The payload - which is the API response - is spread across the return object(the new state value). The code block updates the "total", "initialTotal", "filteredUsers" and "users" values while the rest states maintain their previous value.

Go to your browser, the table now displays the users.

hh.PNG

QUERY_CHANGED

We have to handle the "skip" and "take" values when a user either clicks a pagination button or changes the number of items per page.

Adding the dispatch function to your code in the "src/pages/index.js". It will now be like this

...
<Paginator onPageClick={(
        {queryName, queryValue})=>
        dispatch({type:'QUERY_CHANGED', payload:{
          queryName, queryValue
        }})
      } currentPageIndex={state.skip/state.take} take={state.take}
        total={state.total}
/>

Go over to the "src/reducer/paginatorReducer.jsx".

add this

...
export const paginatorReducer = (state, action) => {
...
switch (action.type) {
  ...
   case "QUERY_CHANGED":
        return {
          ...state,
          [action.payload.queryName]: action.payload.queryValue,
        }
      default:  return state;
};

You can see that if you click on a pagination button or you change the number of items per page, an object is passed with a "queryName" value of "skip" and "take" respectively, including the "queryValue" value.

The code block updates the value accordingly.

Go on and test on your browser

day1usereducer.PNG

FILTER_APPLIED

The filter form has to be taken care of. In the event of a dispatch, the "filteredUsers" will change to conform to the filter values while the "users" remain the same.

Your "FilterForm" section in the "src/pages/index.jsx" will now be like this

{showFilter &&
   <FilterForm
     exFilterData={state.filter} 
     hideForm={()=>setShowFilter(false)}
     addFilter={(data)=>
     dispatch({type:'FILTER_APPLIED', payload:data})}
/>}

Go over to the "src/reducer/paginatorReducer.jsx".

add this

...
export const paginatorReducer = (state, action) => {
   switch (action.type) {
      ...
      case "FILTER_APPLIED":
          console.log(action.payload,'filter applied')
          let usersCopy=state.users
          let new_filter= action.payload
          for (const key in action.payload) {
              const element = action.payload[key];
              usersCopy=usersCopy.filter(user=>user[key]===element)
          }
          return {
              ...state,
              total:usersCopy.length,
              filteredUsers:usersCopy,
              filter:{...INITIAL_STATE.filter, ...new_filter},
              skip:0
          };
        default:  return state;
    }
}

This demonstrates that some logic can be applied before the return block. I will have to bring in the "users" state, which has the whole values, for each "FILTER APPLIED" operation. The user items that don't meet the requirement will be filtered away after iterating over the "action.payload" object. Note that:

  • Any field that was left empty will be removed before calling the "dispatch" function.
  • If a "userId" and an "id" field were entered, the "action.payload" will be { userId: //userId value, id: //id value }
  • The "filteredUsers", "total" and "skip" changes. The "filter" updates its value from the
    "action.payload" while the fields that were left empty stay empty.

Test the filter box in your browser. Click on the "show/hide filter" button to display the filter form.

day3filter.PNG

RESET

We have to reset the table and the state values when the reset button is clicked.

Your "FilterForm" section in the "src/components/table.jsx" will now be like this ...

<FilterForm
        exFilterData={state.filter} 
        hideForm={()=>setShowFilter(false)}
        reset={()=>
          dispatch({type:'RESET'})}
        addFilter={(data)=>
          dispatch({type:'FILTER_APPLIED', payload:data})}
/>

Go over to the "src/hooks/paginatorReducer.jsx".

add this

...
export const paginatorReducer = (state, action) => {
  ...  
  switch (action.type) {
      case "RESET":
        return {
          ...INITIAL_STATE,
          initialTotal:state.initialTotal,
          users:state.users,
          total:state.initialTotal,
          filteredUsers:state.users
        };
    }
   default:
        return state;
    }
}

We return almost everything back to its initial state except those that were updated when "USERS_FETCHED" was called.

The 2 files go like this -

src/reducer/paginatorReducer.js

export const INITIAL_STATE = {
    filteredUsers:[],
    users:[],
    skip:0,
    take: 10,
    total:1,
    initialTotal:1,
    filter:{
        org:"",
        userName:"",
        email:"",
        date:"",
        phone:"",
        status:""
    }
  };

export const paginatorReducer = (state, action) => {
  switch (action.type) {
      case "USERS_FETCHED":
        return {
          ...state,
          total:action.payload.length,
          initialTotal:action.payload.length,
          filteredUsers:action.payload,
          users:action.payload
        }; 
        case "QUERY_CHANGED":
          return {
            ...state,
            [action.payload.queryName]: action.payload.queryValue,
          }   
        case "FILTER_APPLIED":
          console.log(action.payload,'filter applied')
          let usersCopy=state.users
          let new_filter= action.payload
          for (const key in action.payload) {
              const element = action.payload[key];
              usersCopy=usersCopy.filter(user=>user[key]===element)
          }
          return {
              ...state,
              total:usersCopy.length,
              filteredUsers:usersCopy,
              filter:{...INITIAL_STATE.filter, ...new_filter},
              skip:0
          };
        case "RESET":
          return {
            ...INITIAL_STATE,
            initialTotal:state.initialTotal,
            users:state.users,
            total:state.initialTotal,
            filteredUsers:state.users
        };
        default:  return state;
      }
};

src/pages/index.js

import axios from "axios";
import { useEffect, useReducer, useState} from "react";
import { INITIAL_STATE, paginatorReducer } from "../reducer/paginatorReducer";


export default function Maintable() {  
  const [showFilter, setShowFilter]= useState(false)
  useEffect(() => {
    const userFetched=async()=>{
      const response= await axios.get('https://jsonplaceholder.typicode.com/posts')
      console.log(response)
      return dispatch({type:'USERS_FETCHED', payload:response.data})
    }
    userFetched()
  }, []);     

  if(!state.users) return null //changes here 
  return (
    <div className="maintableContainer">
      {/* changes here */}
      <Table setShowFilter={()=>setShowFilter(!showFilter)} 
        users={state.filteredUsers.slice(state.skip,state.take+state.skip)}/> 
      {/* changes here */}
      <Paginator onPageClick={(
        {queryName, queryValue})=>
        dispatch({type:'QUERY_CHANGED', payload:{
          queryName, queryValue
        }})
      } currentPageIndex={state.skip/state.take} take={state.take}
        total={state.total}
      />
      {showFilter &&
      <FilterForm
        exFilterData={state.filter} 
        hideForm={()=>setShowFilter(false)}
        addFilter={(data)=>
          dispatch({type:'FILTER_APPLIED', payload:data})}
        reset={()=>
          dispatch({type:'RESET'})}
      />}
    </div>
    )
}


function Table({users, setShowFilter}) {

  const cols=[
    "UserId", "Id", "Title", "Body"
  ]
  return (
    <div>
      <button onClick={setShowFilter}>
        Filter/Hide Box
      </button>
      <table className="table">
      <thead>
          <tr>
          {cols.map((col, index) => (
              <th key={index}>
                <div style={{display:"flex", columnGap:"3px"}}>
                  <p>{col}</p> 
                </div>

              </th>
          ))}
          </tr>
      </thead> 
      <tbody>
          {users.map((user={}, key) => {
          return (
            <tr key={key} style={{position:"relative"}}>
            <td>{user.userId}</td>

            <td>{user.id}</td>
            <td>{user.title.slice(0,30)+' ...'}</td>
            <td>{user.body.slice(0,30)+' ...'}</td>

          </tr>
          );
          })}
      </tbody>
      </table>
    </div>
  );
}
function Paginator({take,total, onPageClick, currentPageIndex}) {
  const rowChoice=Array.from({length: 100}, (_, i) => i+1);
  const pageNums= Array.from({length: Math.ceil(total/take)}, (_, i) => i+1);
  return (
  <div className="pagdiv">
      <div className='flex'>
          <p>showing</p>
          <Select value={take} options={rowChoice} onChange={(take)=>onPageClick({queryName:"take", queryValue:take})}/>
          <p>out of 100</p>
      </div>
      <div className='flex' style={{wordWrap:"break-all", maxWidth:"200px",flexWrap:"wrap"}}>
          {pageNums.map((pInd,ind)=>             
              <p key={ind} style={{cursor:"pointer"}} onClick={pInd===currentPageIndex+1?null:()=>onPageClick({queryName:"skip", queryValue:(pInd-1)*take})}
                className={pInd===currentPageIndex+1?'currentpagp':'pagp'}>{pInd}</p>                        
          )}
      </div>       
  </div>
  )
}


function FilterForm({hideForm, addFilter, reset, exFilterData}) {
  const [filterForm, setFilterForm]= useState({...exFilterData})
  const filterAction=()=>{
    let dataToBeFiltered={}
    const {userId, id, title}= filterForm
    console.log({userId, id ,title})
    for (const key in filterForm) {
      if(filterForm[key]){
        if(key=="userId" || key =="id"){
          dataToBeFiltered[key]=parseInt(filterForm[key])
        }
        else{
          dataToBeFiltered[key]=filterForm[key]
        }
      }            
    }
    console.log({dataToBeFiltered})

    if (Object.keys(dataToBeFiltered).length){
        addFilter(dataToBeFiltered)
        // hideForm()
    }
  }
  const resetAction=()=>{
    setFilterForm({
        ...initialFilter
    })
    reset()
    hideForm()
  }
return (
<div className='filterForm'>
  <div>
      <p>UserId</p>
      <input type="number" onChange={(e)=>setFilterForm({
          ...filterForm, userId:e.target.value
      })} value={filterForm.userId} placeholder="userId"/>
  </div>
  <div>
      <p>Id</p>
      <input type="number" placeholder='id' onChange={(e)=>setFilterForm({
          ...filterForm, id:e.target.value
      })} value={filterForm.id}/>
  </div>
  <div>
      <p>Title</p>
      <input type="text" name='title' onChange={(e)=>setFilterForm({
          ...filterForm, title:e.target.value
      })} value={filterForm.title} placeholder="title"/>
  </div>

  <div className='btnsection'>
      <button className='reset' onClick={resetAction}>Reset</button>
      <button className='filter' onClick={filterAction}>Filter</button>
  </div>
</div>
);
}
function Select({options, onChange, value,
}) {
  const [show,toggle]= useState(false)

  return (   
  <div style={{position:"relative"}} onClick={() => toggle(!show)}>
  <div className='optInp'>
      <p className='labelClass'>{value}</p>
      <div className='svg'>
          {show ? (
          <svg
              width="12"
              height="8"
              viewBox="0 0 14 8"
              fill="none"
              xmlns="http://www.w3.org/2000/svg"
          >
              <path
              d="M6.91626 2.36902L12.1453 7.56091C12.3313 7.74582 12.5829 7.84961 12.8452 7.84961C13.1075 7.84961 13.3592 7.74582 13.5453 7.56091C13.6369 7.47016 13.7096 7.3622 13.7593 7.24316C13.8089 7.12413 13.8345 6.9964 13.8345 6.86743C13.8345 6.73846 13.8089 6.61073 13.7593 6.4917C13.7096 6.37266 13.6369 6.2647 13.5453 6.17395L7.61719 0.28894C7.43114 0.104033 7.17954 0.000244141 6.91724 0.000244141C6.65493 0.000244141 6.40333 0.104033 6.21729 0.28894L0.289185 6.17395C0.197543 6.2647 0.124842 6.37266 0.0751953 6.4917C0.0255486 6.61073 0 6.73846 0 6.86743C0 6.9964 0.0255486 7.12413 0.0751953 7.24316C0.124842 7.3622 0.197543 7.47016 0.289185 7.56091C0.475358 7.74558 0.727033 7.84924 0.989258 7.84924C1.25148 7.84924 1.50304 7.74558 1.68921 7.56091L6.91626 2.36902Z"
              fill="#809FB8"
              />
          </svg>
          ) : (
          <svg
              width="12"
              height="8"
              viewBox="0 0 14 8"
              fill="none"
              xmlns="http://www.w3.org/2000/svg"
          >
              <path
              d="M6.91626 5.48059L12.1453 0.288696C12.3313 0.103789 12.5829 0 12.8452 0C13.1075 0 13.3592 0.103789 13.5453 0.288696C13.6369 0.379447 13.7096 0.487411 13.7593 0.606445C13.8089 0.72548 13.8345 0.853205 13.8345 0.982178C13.8345 1.11115 13.8089 1.23888 13.7593 1.35791C13.7096 1.47694 13.6369 1.58491 13.5453 1.67566L7.61719 7.56067C7.43114 7.74558 7.17954 7.84937 6.91724 7.84937C6.65493 7.84937 6.40333 7.74558 6.21729 7.56067L0.289185 1.67566C0.197543 1.58491 0.124842 1.47694 0.0751953 1.35791C0.0255486 1.23888 0 1.11115 0 0.982178C0 0.853205 0.0255486 0.72548 0.0751953 0.606445C0.124842 0.487411 0.197543 0.379447 0.289185 0.288696C0.475358 0.104031 0.727033 0.000366211 0.989258 0.000366211C1.25148 0.000366211 1.50304 0.104031 1.68921 0.288696L6.91626 5.48059Z"
              fill="#809FB8"
              />
          </svg>
          )}
      </div>        
  </div>

    {show && (
      <div className='listcontainer'>
      <ul>
        {options.map((option,index)=>
          <li key={index} 
          style={{color: '#666666'}}
          onClick={()=>onChange(option)}
          className='formopt'>{option}</li>           
        )}
      </ul>
      </div>
    )}
  </div>
)}

The link to the source code is here

Thought Process

An excellent alternative for managing complex states is useReducer. Use cases where using useReducer is preferable to using useState include shopping carts, to-do lists, calculators, multi-step forms with progress indicators, and more.

My time has come; Thanks for having me.

If you have any questions, ask me in the comments section and I'll try to answer as soon as possible.