TLDR; Here is the code

export const usePrevRef = <T>(value: T) => {
	const currentRef = useRef<T>()
	const prevPref = useRef<T>()
	prevPref.current = currentRef.current
	currentRef.current = value
	return prevPref
}

Why do we need two refs to accomplish such a look-simple task?

Longer story

Imagine that there are 3 phases in an useEffect hook.

  • Render phase: in the render function, outside the useEffect body
  • Effect phase: in the useEffect body, outside the disposal function
  • Disposal phase: in the disposal function

In the caller hook, to have the ref returned from usePrevRef reflected the correct value in all these 3 phases, the update must happen before caller's render(⑧) and after caller's disposal(⑥).

From currentRef's point of view, in the above code, currentRef.current holds the value in the previous render, waits until the next render, and passes its value to prevRef.

In ⑧,⑩,⑫, we want the ref returned from usePrev reflecting the value in the previous render. Which means the value must be updated in ①,③,⑤or⑦.

If the update happens in ①,③,or⑤, in ⑥ (similarly, in ⑫), the new value is used, and this is not what we want. Hence, we must carry out the update in ⑦.

Obviously, we can not use value directly because it is the up-to-date value. We want the previous value one, which should be stored in any phase in the previous render(i.e., ①or③or⑤).