Here is the link to my working demo
Link to my code
By this code what I am trying to achieve is to get clarity to
- How to have Fewer Render hence better performance (possibly by introducing useRef instead of useEffect)
- Eliminating Unnecessary API calls (when applying the same filter or when clicking reset again and again shouldn't trigger an API call)
- Right way to use useReducer and check for anti-patterns in my custom hook
my rendering page looks like below where useGetData is my custom hook and gives all the value and function to complete data and UI functionalities
import { DemoTable, MultiSelect } from "../../components"
import { useGetData } from "./useGetData"
import styled from 'styled-components'
import {useRef } from "react"
export const RerenderPage = () => {
const count = useRef<number>(0)
const {bugsData,BUGS_COLUMN,handleApply,HandleMultiSelect,totalCount,page,pages,filters,filterState,resultLimit,handlePagination,handleReset} = useGetData()
console.log('rendering,page for count :',count.current)
return <div>
<div style={{display:"flex",justifyContent:"space-between"}}>
<p>Results found {totalCount}</p>
<div style={{display:"flex",gap:'8px'}}>
<p>Page: {`${page}/${pages}`}</p>
<select value={resultLimit} onChange={(e:React.ChangeEvent<HTMLSelectElement>) => handlePagination('resultCount',e.target.value)}>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
</select>
</div>
</div>
<SelectionGrid>
<MultiSelect
handleSelect={HandleMultiSelect}
options={filters.assignedTo.map(assign => {
return {
key:'assignedTo',
value:assign
}
})}
selected={filterState}
/>
<MultiSelect
handleSelect={HandleMultiSelect}
options={filters.status.map(assign => {
return {
key:'status',
value:assign
}
})}
selected={filterState}
/>
</SelectionGrid>
<button onClick={handleApply}>
Apply
</button>
<button onClick={handleReset}>
Reset
</button>
<DemoTable columns={BUGS_COLUMN} data={bugsData}/>
</div>
}
const SelectionGrid = styled.div`
display:grid;
grid-template-columns: 1fr 1fr 1fr;
gap:8px;
`
import { useCallback, useEffect, useMemo, useReducer, useState } from "react"
import { getBugs } from "../../utils/MockApiController"
declare interface IKeyValue {
key:string,
value:string
}
declare interface IInputBugData {
filters:IKeyValue[],
page:number,
resultLimit:number
}
declare interface IBug {
id: number;
module: string;
platform: string;
rootCause?: string;
reportedBy: string;
assignedTo: string;
status: string;
defectCategory: string;
severity: string;
priority: string;
}
declare interface IFilterBugData {
platform: string[];
rootCause: string[];
reportedBy: string[];
assignedTo: string[];
status: string[];
defectCategory: string[];
severity: string[];
priority: string[];
}
declare interface IFilterPayload {
action:string
}
export const useGetData = () =>{
const [page,setPage] = useState(1)//current page filter
const [resultLimit,setResultLimit] = useState(10) //result limit filter,currently not implemented
const [totalCount,setTotalCount] = useState(0) // total result count filter
const [filters,setFilters] =useState<IFilterBugData>({
platform:[],
rootCause: [],
reportedBy: [],
assignedTo: [],
status: [],
defectCategory: [],
severity: [],
priority: [],
})//filter options
const [bugsData,setBugsData] = useState<IBug[]>([]) //bugs result
const handleFilterDispatch = (state:IKeyValue[],payload:IFilterPayload) => {
let _temp = [...state]
switch(payload.action){
case 'add':
return _temp
case 'reset':
return []
default:
return _temp
}
}// use reducer for handling function temp filter state
// const filterState = useRef<IKeyValue[]>([])
const [filterState,filterDispatch] = useReducer(handleFilterDispatch,[])//temp filter state
const [finalFilterState,setFinalFiterState] = useState<IKeyValue[]>([])//input filter state
const memoizedInput:IInputBugData = useMemo(() => {
return {
filters:finalFilterState,
page,
resultLimit
}
},[finalFilterState,page,resultLimit])
const getBugsData = useCallback(() => {
console.log('inside memoized callback',memoizedInput)
return getBugs(memoizedInput)
}, [memoizedInput])
//column data for demo table
const BUGS_COLUMN = [
{
key:'id',
title:'Defect ID'
},
{
key:'module',
title:'Module'
},
{
key:'platform',
title:'Platform'
},
{
key:'severity',
title:'Severity'
},
{
key:'priority',
title:'Priority'
},
{
key:'reportedBy',
title:'Reported By'
},
{
key:'assignedTo',
title:'Assigned To'
},
{
key:'status',
title:'Status'
},
{
key:'defectCategory',
title:'Defect Category'
},
{
key:'rootCause',
title:'Root Cause'
}
]
/**
* handles selection/de-selection of option from multiselect component
* @param option - options selected
*/
const HandleMultiSelect = (option:IKeyValue) => {
const optionIndex = filterState.findIndex(filter => filter.key === option.key && filter.value === option.value)
optionIndex === -1?filterState.push(option):filterState.splice(optionIndex,1)
filterDispatch({action:'add'})
}
const handleApply = () => {
setFinalFiterState(filterState)
}
const handleReset = () => {
filterDispatch({action:'reset'})
setFinalFiterState([])
}
const handlePagination = (paginationType:string,paginationValue:string|number) => {
switch(paginationType){
case 'resultCount':
setResultLimit(paginationValue as number)
break
case 'page':
setPage(paginationValue as number)
break
default:
setPage(paginationValue as number)
}
}
useEffect(() => {
console.log('rendering dataaaaaa')
let {resultCount,currentPage,bugs,filters} = getBugsData()
setBugsData(bugs)
setFilters({...filters})
setPage(currentPage)
setTotalCount(resultCount)
},[getBugsData])
return {bugsData,totalCount,filters,pages:Math.ceil(totalCount/resultLimit),HandleMultiSelect,handleApply,BUGS_COLUMN,page,resultLimit,filterState,handlePagination,handleReset}
}
currently, if I apply filter and click filter again or set the same result limit it doesn't call API (mock), but if I select and deselect the same filters it will cause an API call even though It's the same as the previous filter. Also, every reset invokes an API call.
Need to find a way to eliminate these API calls and check if I can replace any useState with useRef or useReducer because of performance/anti-pattern issues