I am working on a reusable filter component that dynamically creates a list of checkboxes within a dropdown, and includes an "all selected" toggle (similar to google flights). Everything is working as desired, but there is huge latency when clicking a checkbox, and especially when using the select all toggle. It is very similar to the issue described here (almost identical even), but I have tried my best to implement the solutions described therein to no avail. I tried applying useCallback on all the functions that are passed, and gotten rid of all warnings from 'why-did-you-render' but none of it resolved the issue. I'm hoping somebody here can help tell me what I'm missing. See relevant code snippets below. Thanks!
This is the parent component that manages the data:
import React, { useState, useEffect } from "react";
import ControlPanel from '../Components/ControlPanel';
const TopLevelComponent = () => {
const [data, setData] = useState();
const [selectedFilters, setSelectedFilters] = useState([]);
useEffect(() => {
getData();
}, [])
const getData = async() => {
...
}
return (
<div>
<ControlPanel
filterOptions={ data?.filterSet }
filterSelections={ selectedFilters }
filterHandler={ setSelectedFilters }
/>
{ /* other stuff irrelevant here */ }
</div>
)
}
export default TopLevelComponent;
This is a reusable control panel component that holds the filters and other settings:
import React from "react";
import { Filter } from './Inputs';
const ControlPanel = ({ filterOptions, filterSelections, filterHandler }) => {
return (
<>
{ /* other controls not relevant here */ }
<div className="control-panel__filters">
<Filter
filterName="Filter A"
options={ filterOptions }
handler={ filterHandler }
selections={ filterSelections }
selectAllLabel="Select all filter A"
/>
</div>
{ /* more irrelevant stuff */ }
</>
)
}
export default ControlPanel;
and finally, this is where the filters and the components that make them up are defined:
import React, { useState } from "react";
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem, Label } from 'reactstrap';
import Toggle from 'react-toggle';
import chevronDown from '../icons/chevron-down-dark.svg';
import chevronUp from '../icons/chevron-top.svg';
import closeIcon from '../icons/close.svg';
export const Filter = ({ options, handler, selections, filterName, selectAllLabel }) => {
const [dropdownOpen, setDropdownOpen] = useState(false);
const toggle = () => setDropdownOpen(prevState => !prevState);
return (
<Dropdown isOpen={ dropdownOpen } toggle={ toggle } >
<DropdownToggle>
{ filterName }
<img src={ dropdownOpen ? chevronUp : chevronDown } />
</DropdownToggle>
<DropdownMenu>
<FilterInterface
toggle={ toggle }
filterName={ filterName }
options={ options }
selections={ selections }
handler={ handler }
selectAllLabel={ selectAllLabel }
/>
</DropdownMenu>
</Dropdown>
);
}
const FilterInterface = ({ toggle, filterName, options, selections, handler, selectAllLabel }) => {
const [allSelected, setAllSelected] = useState(false);
const toggleAllSelected = () => {
if (options.length !== selections.length) {
const newSelections = options.map(option => option.id);
handler(newSelections);
setAllSelected(true);
} else {
handler([]);
setAllSelected(false);
}
}
const handleCheck = id => {
let newArray;
if (selections.includes(id)) {
newArray = selections.filter(selection => {
return selection !== id;
});
setAllSelected(false);
} else {
newArray = [...selections, id];
}
handler(newArray);
if (newArray.length === options.length) setAllSelected(true);
}
return (
<>
<DropdownItem header>
{ filterName }
<img src={ closeIcon } onClick={ toggle } />
</DropdownItem>
<DropdownItem header>
{selectAllLabel}
<Toggle
checked={ allSelected }
icons={ false }
onChange={ toggleAllSelected }
/>
</DropdownItem>
{
options?.map(option => (
<DropdownItem header
key={ option.id }
value={ option.name }
className="filterCheckbox"
>
<Label onClick={ () => handleCheck(option.id) }>
<Checkbox checked={ selections.includes(option.id) } readOnly />
<span>{ option.name }</span>
</Label>
</DropdownItem>
))
}
</>
)
}