I am trying to create a filter component that reads filters values from the search params. I want to set just the defaultChecked
state of the checkboxes on mount.
Component looks like this:
import "./styles.css";
import {
Checkbox,
FormControlLabel,
Box,
FormGroup,
Button,
} from "@mui/material";
import { useSearchParams } from "react-router-dom";
const filterValues = [
{
value: 1,
label: "One",
},
{
value: 2,
label: "Two",
},
] as const;
export default function Filters() {
const [searchParams, setSearchParams] = useSearchParams();
const onFilterChange = (
event: React.ChangeEvent<HTMLInputElement>,
checked: boolean
) => {
let filters = searchParams.get("filters")?.split(",") || [];
const filter = event.target.value;
if (checked) filters.push(filter);
else filters = filters.filter((_filter) => filter !== _filter);
if (filters.length === 0) searchParams.delete("filters");
else searchParams.set("filters", filters.join(","));
};
return (
<Box>
<FormGroup>
{filterValues.map(({ label, value }) => (
<FormControlLabel
key={label}
control={
<Checkbox
defaultChecked={searchParams
.get("filters")
?.split(",")
.includes(value.toString())}
onChange={onFilterChange}
/>
}
value={value}
label={label}
/>
))}
</FormGroup>
<Button variant="contained" onClick={() => setSearchParams(searchParams)}>
Set Filters
</Button>
</Box>
);
}
The problem is, when I set the new search params after setting up the filters I get the following error:
MUI: A component is changing the default checked state of an uncontrolled SwitchBase after being initialized.
Codesandbox example:
The possible solution I see is to save initial checked states using useState
, which would read all filters from the url, like this:
const [checkedStates] = useState(
filterValues.map(({ value }) => ({
checked: searchParams.get('filters')?.split(',').includes(value.toString()),
})),
);
and setting the defaultChecked
from it:
<Checkbox defaultChecked={checkedStates[index].checked}/>
It feels like unnecessary state duplication to me. Is there a better way of handling this?
2
Answers
So, based on the @MGreif answers I understand that I need to make the checkboxes controlled components and use the
checked
prop instead adefaultChecked
. So what I've done is:I created a new state to read the filter values from search params:
Then in the checkbox
onChange
handler I modify the state:And then if all filters are set correctly, and the
Set Filters
button is clicked I call thesetSearchParams
to navigate to the new url and fire new HTTP request:Setting the SearchParams in your
onFilterChange
callback will cause theuseSearchParams
to reset its state, thus rerendering your component. This will rerender your MUI Checkboxes and set a different value as thedefaultChecked
value.I believe using the
checked
prop instead of thedefaultChecked
prop would solve the issue. Unfortunately i cannot edit your sandbox to test it myself.As you are just manipulating the searchParams yourself while staying on the same page, i think using a dedicated state (which changes on each checkbox click), would fit better, as you dont have to "apply" the filter anymore. I dont really know your usecase
Hope this helps somehow,
Mika