0
\$\begingroup\$

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

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.