skip to Main Content

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


  1. 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

    const onApply = async (appliedFilters: FilterProps) => {
        await fetchFilteredData(appliedFIlters, setFilter)
    }
    

    Example: fetchFilteredData Function

    const fetchFilteredData = async (params: FilterProps, callBackFunc?: (appliedFilters: FilterProps) => void) => {
        try {
            await axios.get(url, params);
            callBackFunc?.()
        } catch (error) {
            console.error('Error is: ', error)
        }
    }
    

    Or, you can also modify only your onApply function as,

    const onApply = async (appliedFilters: FilterProps) => {
        setFilter(appliedFIlters)
        await fetchFilteredData(appliedFIlters)
    }
    

    But this will not be suggested. As, if API get fails, the filter will still be updated with the Applied filters.

    Login or Signup to reply.
  2. Based on your CodeSandbox Demo Link

    I think the issue is in InputBoxFilter component where you have 2 state to capture the filterValue.

    const { filters, setFilter, applyFilters } = useFilters();
    const [value, setValue] = React.useState(filters[filterKey] || "");
    

    If you don’t need a local state in the component, you can directly use filters to control your component

    <input value={filters[filterKey]} />
    

    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:

    1. In the provider instantiate the filter with default values to make the inputs controlled inputs. Or <input value={filters[filterKey] || ''} /> will work as well.
    2. You cannot verify the solution in the codesandbox example as the trigger happens right after apply fn in the old InputBoxFilter component, but should be verifiable in the env where you are clicking the button fast enough (thus allowing to rerender the InputBoxFilter).
    3. The context solution is async in nature by design. If the context update is so slow so that the time taken between the keyboard stroke and the value displayed in the input component is not acceptable, then you have to think about other sync solutions.
    4. Sync solutions examples are usage for useRef, saving the date as url param, saving the data in global object etc. Let me know if you want to explore in those direct even though I recommend the async option.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search