0

could you provide your feedback on the code below:

export function useUnmountSafeReducer<R extends Reducer<any, any>>(
  reducer: R,
  initialState: ReducerState<R>,
  initializer?: undefined
): [ReducerState<R>, Dispatch<ReducerAction<R>>] {
  const [mounted, setMounted] = useState(true);
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    return () => {
      setMounted(false);
    };
  }, []);

  return [state, mounted ? dispatch : () => {}];
}

I am trying to write own reducer which will not use dispatch if component is unmounted.

Mykyta
  • 230
  • 2
  • 5
  • 16

1 Answers1

2

Try with a ref instead of a state.

  const mounted = useRef(true)

  useEffect(() => {
    return () => {
      mounted.current = false
    }
  }, [])

The reason is that using setMounted is a memory leak used in the destroy function of useEffect. Keep in mind if the component is unmounted, you are not supposed to use any internal method after that. Actually avoiding the memory leak is your reason to implement this mounted at the first place, isn't it?

disabled dispatch

Now the question is can you return a new dispatch after the unmount?

  return [state, mounted ? dispatch : () => {}]

After the unmount, there probably won't be any more update to the UI . So the way to get it working is to disable the existing dispatch but not providing an empty one.

  const _dispatch = useCallback((v) => {
    if (!mounted || !mounted.current) return
    dispatch(v)
  }, [])

  return [state, _dispatch]

The useCallback there might be optional.

windmaomao
  • 7,120
  • 2
  • 32
  • 36
  • it shouldn't but if it does, take out of `dispatch` from the dependency array, so leaving it as `}, [])`. In theory, it shouldn't because `dispatch` is not going to change after the initial mount. – windmaomao Sep 17 '21 at 12:41
  • You are welcome and if you believe the above solves your problem, please accept the answer. thank you. – windmaomao Sep 17 '21 at 13:20