How to make a page that Filters data on multiple conditions based on user input

Viewed 996

The idea was to create an advanced search page that would enable users to filter data as per their choice by selecting as many as five options to narrow down their search. The results would then later get displayed on a table. Having said that, a user may decide to filter by either using only a single option i.e By state, or by taking multiple options i.e By Name, By District, and By Category or as said earlier up to 5 options. However, I have no way of figuring out how this can be done. You would find screenshots followed by the code below:

A scenario where the user chooses to filter using the By State option

enter image description here

Scenario, where the options are chosen are multiple to narrow down the search

enter image description here

The code is as follows:

    const AdvanceFilterScreen = () => {
    const [byNameFilter, setByNameFilter] = useState(false);
    const [name, setName] = useState('');

    const [byDateFilter, setByDateFilter] = useState(false);

    const [byCategoryFilter, setByCategoryFilter] = useState(false);
    const [category, setCategory] = useState('');

    const [byDistrictFilter, setByDistrictFilter] = useState(false);
    const [district, setDistrict] = useState('');

    const [byStateFilter, setByStateFilter] = useState(false);
    const [status, setStatus] = useState('');

    const [bySerialNumberFilter, setBySerialNumberFilter] = useState(false);
    const [serialNumber, setSerialNumber] = useState('');

    const [byIssueFilter, setbyIssueFilter] = useState(false);

    const [buttonValue, setFilterButtonValue] = useState('');

    //What the following methods do is set the value of setFilterButtonValue to '' so that 
    //the value of the current selected option turns to blank upon de-selecting them
    //On-Click methods that store the values to choiceArray
    const nameFilter = () => {
        setByNameFilter(prev => !prev);
        setFilterButtonValue('');
        setName('')
    }

    const categoryFilter = () => {
        setByCategoryFilter(prev => !prev);
        setFilterButtonValue('');
        setCategory('')
    }

    const districtFilter = () => {
        setByDistrictFilter(prev => !prev);
        setFilterButtonValue('');
        setDistrict('')
    }

    const stateFilter = () => {
        setByStateFilter(prev => !prev);
        setFilterButtonValue('');
        setStatus('')
    }

    const serialNumberFilter = () => {
        setBySerialNumberFilter(prev => !prev);
        setFilterButtonValue('');
        setSerialNumber('')
    }

    return (
        <div className="advanceFilterScreen">
            <div className="navBarFilter">
                <NavBar></NavBar>
            </div>
            <div className="chooseFilters">
                <div className="chooseColumn">
                    <div className="caption">Choose Filters (Upto 5 filters at a time)</div>
                    <div className="filterOptions">
                        <div onClick={nameFilter}>
                            <div className={byNameFilter ? "nameSelectedFilter" : "byNameFilter"}>By Name</div>
                        </div>

                        <div onClick={() => setByDateFilter(prev => !prev)}>
                            <div className={byDateFilter ? "dateSelectedFilter" : "byDateFilter"}>By Date</div>
                        </div>

                        <div onClick={categoryFilter}>
                            <div className={byCategoryFilter ? "categorySelectedFilter" : "byCategoryFilter"}>By Category</div>
                        </div>

                        <div onClick={districtFilter}>
                            <div className={byDistrictFilter ? "districtSelectedFilter" : "byDistrictFilter"}>By District</div>
                        </div>

                        <div onClick={stateFilter}>
                            <div className={byStateFilter ? "stateSelectedFilter" : "byStatesFilter"}>By States</div>
                        </div>

                        <div onClick={serialNumberFilter}>
                            <div className={bySerialNumberFilter ? "serialNumberSelectedFilter" : "bySerialNumberFilter"}>By Serial Number</div>
                        </div>

                        <div onClick={() => setbyIssueFilter(prev => !prev)}>
                            <div className={byIssueFilter ? "issueSelectedFilter" : "byIssueFilter"}>By Issue</div>
                        </div>
                    </div>
                </div>
            </div>

            <div className={(byNameFilter || byDateFilter || byCategoryFilter || byDistrictFilter || byStateFilter || bySerialNumberFilter || byIssueFilter) ? "filterDropdowns" : "noFilter"}>
                <div className="filterClass">
                    {byNameFilter ? <div style={{ marginRight: "15px" }}>
                        <NameFilter setName={setName}></NameFilter>
                    </div> : null}
                    {byDateFilter ? <div style={{ marginRight: "15px" }}>Search By Date</div> : null}
                    {byCategoryFilter ? <div style={{ marginRight: "15px" }}>
                        <CategoryFilter setCategory={setCategory}></CategoryFilter>
                    </div> : null}
                    {byDistrictFilter ? <div style={{ marginRight: "15px" }}>
                        <DistrictFilter setDistrict={setDistrict}></DistrictFilter>
                    </div> : null}
                    {byStateFilter ? <div style={{ marginRight: "15px" }}>
                        <StatusFilter setStatus={setStatus}></StatusFilter>
                    </div> : null}
                    {bySerialNumberFilter ? <div style={{ marginRight: "15px" }}>
                        <SerialNumberFilter setSerialNumber={setSerialNumber}></SerialNumberFilter>
                    </div> : null}
                    {byIssueFilter ? <div>Search By Issue</div> : null}
                </div>
                <div>
                    <FilterSearchButton name={name} category={category} district={district} status={status} serialNumber={serialNumber} setFilterButtonValue={setFilterButtonValue}></FilterSearchButton>
                </div>
            </div>
            <div style={{ marginTop: "50px" }}>
                {district ? <DistrictTable district={district}></DistrictTable> : null}
                <Table buttonValue={buttonValue}></Table>
            </div>
        </div>
    );
};

export default AdvanceFilterScreen;

FilterSearchButton component This is where the filtering occurs using the props received from the selected options. Right now, this component is configured only to filter using one prop value (i.e either using the name, category, district props etc.). What should I do to filter using multiple conditions using? Just to remind you again, the selected options have been passed on as props to this component.

    const FilterSearchButton = ({ name, category, district, status, serialNumber, setFilterButtonValue }) => {
    const [multiFilter, setMultiFilter] = useState([]);

    useEffect(() => {
        setMultiFilter(dummyData.flatMap(value => value.ward_no.flatMap(
            valueArray => ['grievances', 'general', 'urgent', 'services'].flatMap(
                value => valueArray[value]))).filter(value => value.name === name ||
                    value.districtName === district ||
                    value.type === category ||
                    value.status === status ||
                    value.serial_number === serialNumber
                )
        )
    }, [name, category, district, status, serialNumber, setFilterButtonValue]);

    const searchFilter = () => {
        setFilterButtonValue(multiFilter)
    }

    return (
        <div className="searchFilterButton" onClick={searchFilter}>Search</div>
    );
}

export default FilterSearchButton;

P.S: dummyData is a JSON file on which the filtering occurs. The ['grievances', 'general', 'urgent', 'services'] represents the keys over which the filtering occurs. An extract of the JSON data is below. I have removed most of the properties to keep them short.

[
    {
        "district": "Kolkata",
        "ward_no": [
            {
                "ward": "6",
                "grievance": [
                    {
                        "serial_number": "0001",
                        "name" : "Mr.A"
                    },
                    {
                        "serial_number": "0002",
                        "name" : "Mr.B"
                    }
                ],
                "general": [
                    {
                        "serial_number": "0003",
                        "name" : "Mr.C"
                    },
                    {
                        "serial_number": "0004",
                        "name" : "Mr.D"
                    }
                ]
            },
            {
                "ward": "7",
                "grievance": [
                    {
                        "serial_number": "0005",
                        "name" : "Mr.E"
                    },
                    {
                        "serial_number": "0006",
                        "name" : "Mr.F"
                    }
                ],
                "general": [
                    {
                        "serial_number": "0007",
                        "name" : "Mr.G"
                    },
                    {
                        "serial_number": "0008",
                        "name" : "Mr.H"
                    }
                ]
            }
        ]
    },
    {
        "district": "Hooghly",
        "ward_no": [
            {
                "ward": "8",
                "grievance": [
                    {
                        "serial_number": "0009",
                        "name" : "Mr.I"
                    },
                    {
                        "serial_number": "0010",
                        "name" : "Mr.J"
                    }
                ],
                "general": [
                    {
                        "serial_number": "0011",
                        "name" : "Mr.K"
                    },
                    {
                        "serial_number": "0012",
                        "name" : "Mr.L"
                    }
                ]
            },
            {
                "ward": "9",
                "grievance": [
                    {
                        "serial_number": "0013",
                        "name" : "Mr.M"
                    },
                    {
                        "serial_number": "0014",
                        "name" : "Mr.N"
                    }
                ],
                "general": [
                    {
                        "serial_number": "0015",
                        "name" : "Mr.O"
                    },
                    {
                        "serial_number": "0018",
                        "name" : "Bruno Fernandes"
                    }
                ]
            }
        ]
    }
]
3 Answers

This might have a performance impact if your list is really big, but since .filter() returns back an array, why not chain 5 filters together.

For example,

let a = [1,2,3,4,5,6,7,8,9,10]
console.log(a.filter(b => b > 0).filter(c => c > 1).filter(d => d > 2).filter(e => e > 3))

This would print the array - [4,5,6,7,8,9,10]

Wrap you filter logic in a useEffect and so whenever an input changes for the props, the useEffect can run again. Maybe you could improve it by using useCallback as well.

I hope you get the idea and can use it for your solution

try to do a filter that you will add to him more arguments when you start to select more stuff

and then you send it a method that will do something like that

const filterHandler =(data)=>{
  let flag = true;
  if(bySerialNumberFilter){
    flag = data?.ward_no[0]?.grievance?.serial_number  === serialNumber
  }
  return flag;
}

in the data you pass the value from the filter and like this you do if to each of the values that the byState is true then you compare it like that you can add more search on the fly the flag is initial as true because by default you want it to be display you can put it as no but then you need to check if you dont put any filter on

const [filteredData,setFilteredData] = useState([]) //render this list
const [name, setName] = useState('');
const [status,setStatus] = useState('');
const [district, setDistrict] = useState('');
const [status, setStatus] = useState('');

const data=[......]  //original data

const checkInStatus =(obj,value,key)=>{
 const statusList = ['grievances', 'general', 'urgent', 'services'];
 for (let status in statusList){
   if(obj?.[status]?.[key] === value) return true
 }
 return false
}

useEffect(()=>{
//no need to use byname, bycategory etc. Just filter the based on fields that have a value. To remove a filter just set its value to ''.

let filtered = data.filter(e => district ? e.district === district:true ) //return all data if no filter by district

filetered = filtered.filter(e => {
    if(!name) return true;
    if(e[status] === name) return true;
    return checkInStatus(e,name,'name')
   })
},[name,category,district,status])


Only some filters are explained. You can use the same logic for the rest of the filters also

For status no need to filter the data . While rendering just render only the fields for that status.

filteredData.map(e=> e[status].map(c=>c.name)}) // store status as a state
Related