Redux: Heavier logic in Action Creators slows down UI? (Should it be wrapped with setTimeout()?)

Viewed 32

Redux Toolkit Action Creators should expensive logic be in setTimeout()?

Use case there is filtering box which filters data. In the code below there is action creator responsible for first updating the state of the filter box and then updating the filtered data.

When I dont wrap filtering logic in setTimeout there is a noticable lag in the UI of the filtering box (changing the filtering words becomes hindered). So is it advised practice to put heavier logic within the action creators in something like setTimeout()?

export const filteringWordsChanged = (newWords: string) => (dispatch: any, getState: any) => {

    // Send Action to make sure the filter box insides are updated. 
    dispatch(updateFilteringWords(newWords));

    let callback = () => {
        let filtered = performFilteringLogic(getState(), newWords);
        dispatch(updateFilteredResults(filtered));
    };

    // callback();
    setTimeout(callback, 0);
}
1 Answers

So I take it that performFilteringLogic is not async, it's just slow? My instinct is that this is best handled inside the component with the two actions dispatched separately. We want the changes to words to be immediate, while updating the filtered results might take some time (as a sidenote: filtering is usually the sort of thing that is done through a memoized selector rather than by storing the filtered results in your store).

Here, the callback on the input onChange event immediately dispatches updateFilteringWords. That will cause the variable words from the selector to change. The effect listens for changes to words. If words is not the same string that was used to generate the current filtered list, then it does the expensive performFilteringLogic calculation and dispatches the results with updateFilteredResults.

const MyComponent = () => {
  const dispatch = useDispatch();
  const state = useSelector(state => state);
  const words = useSelector(state => state.something.words);
  const filtered = useSelector(state => state.something.filtered);
  // I would store an extra property alongside the filtered results that stores the string of words which were used to generate them
  // We can use this to prevent the effect from running unecessarily, like when the component is mounted
  const filteredBy = useSelector(state => state.something.filteredBy)

  useEffect( () => {
    // don't filter again on the same word list
    if ( words !== filteredBy ) {
      const filtered = performFilteringLogic(state, words);
      // We would pass both the filtered results and the words so that words can be saved to `filteredBy` in the reducer
      dispatch(updateFilteredResults(filtered, words));
    }
  }, [words]);

  return (
    <>
      <input value={words} onChange={(e) => dispatch(updateFilteringWords(e.currentTarget.value))}/>
      <SomeComponent data={filtered}/>
    </>
  )
}
Related