I want to maintain the filter values, which are key value pairs in a global context to support app wide state.
const createFilterContext = (defaultFilters: FilterState = {}) => {
const FilterContext = React.createContext<FilterContextType | undefined>(undefined);
const FilterProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [filters, setFilters] = React.useState<FilterState>(defaultFilters);
const setFilter = (key: string, value: any) => {
setFilters((prevFilters) => ({ ...prevFilters, [key]: value }));
};
const clearFilters = () => {
setFilters(defaultFilters);
};
return (
<FilterContext.Provider value={{ filters, setFilter, clearFilters }}>
{children}
</FilterContext.Provider>
);
};
There is a filter panel which shows all the filter controls
const FilterPanel = ({ filterControls, onApply }: FilterPanelProps) => { }
<FilterPanel filterControls={filterControls} onApply={fetchDataWithFilters} />
FilterControls are some kind of input elements like dropdown, slider, checkbox etc.
onApply is passed by the pages that use FilterPanel. It will run when Apply Filters button in Filter Panel is clicked and will fetch Data with the applied key value pairs as query params to backend.
Now the problem here is React setState is asynchronous. So if I change some filter input control, and immediately click Apply Filters, the context state will be yet to update with the new value from that input control and makes fetch call without the new value.
My real code is much bigger and complex. I have simplified it here.
Please help me with your suggestions. Is my design bad? How can I solve this problem?
CodeSandbox Demo Link
In codesandbox project, I have directly called fetch method immediately after state update code in filter controls, as it was too fast since it is light weight app with fewer lines of code and calling fetch on click of Apply Filters button gives it sufficient time to get new state.
In my Real Large code base it takes few milliseconds for state update and I get the issue when Apply Filters button is pressed.
2
Answers
From the above question, I understand that, on apply you are updating the setState and passing the updated filter values to your API Service as a params.
To better way to handle that situation is to, add a callback inside that API service and then pass the actual parameter as an argument to that service and also pass your setState. So, if your API gets successful, it will also update your state with that value [Don’t depend on the state value on Apply. Depend on the state value for the initial time only]. This will fix your error.
Example: onApply Function
Example: fetchFilteredData Function
Or, you can also modify only your onApply function as,
But this will not be suggested. As, if API get fails, the filter will still be updated with the Applied filters.
Based on your CodeSandbox Demo Link
I think the issue is in InputBoxFilter component where you have 2 state to capture the filterValue.
If you don’t need a local state in the component, you can directly use filters to control your component
This ensures that there is only one state for the input and before clicking the apply button, if you see it in the ui, you can expect the same in the api call.
Note:
<input value={filters[filterKey] || ''} />
will work as well.