Last active
April 27, 2025 07:13
-
-
Save btoo/65e7d4303f49299c785d38f8758525e6 to your computer and use it in GitHub Desktop.
typescript type-safe version of usePrevious taken directly from the react docs https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useRef, useEffect } from 'react'; | |
/** | |
* a type-safe version of the `usePrevious` hook described here: | |
* @see {@link https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state} | |
*/ | |
export function usePrevious<T>( | |
value: T, | |
): ReturnType<typeof useRef<T>>['current'] { | |
const ref = useRef<T>(); | |
useEffect(() => { | |
ref.current = value; | |
}, [value]); | |
return ref.current; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@peckyboy nice question. The return type is
MutableRefObject<T | undefined>['current']
in an effort to duplicate as little type-level information as possible. Using an explicit return type ofT | undefined
would be to assert thatuseRef<T>().current
has the typeT | undefined
(at least based on theusePrevious
function body), butusePrevious
shouldn't have the right to make this assertion when it is expressly using (and more specifically, not declaring)useRef
in its implementation details. Therefore, to be more correct would be to infer theuseRef<T>().current
type as directly as possible from theuseRef<T>().current
runtime code instead. Doing so ensures that, ifuseRef
's implementation ever changes (thus, possibly changing the type ofuseRef<T>().current
) from under our noses, we can maximize the chances that our typings are correct. In effect, usingMutableRefObject<T | undefined>['current']
is more future-proof (and more importantly, more correct) thanT | undefined
.Some might criticize the hardcoding of
T | undefined
inMutableRefObject<T | undefined>['current']
to itself be a duplication of type-level information, and their criticisms would be warranted. There's no gaurantee that the implementation ofusePrevious
returnsMutableRefObject<T | undefined>['current']
. That is, simply by looking at only theusePrevious
function body and not the implementations of any ofusePrevious
's dependencies, likeuseRef
, we can not deterministically claim the return type to beMutableRefObject<T | undefined>['current']
. However, to these criticisms, I would say thatMutableRefObject<T | undefined>['current']
duplicates less type information thanT | undefined
and is therefore still better.Taking this a step further, I intend on updating this file to leverage an upcoming feature to be released in TypeScript 4.7: Instantiation Expressions. With Instantiation Expressions, we won't have to hope that

useRef<T>()
's type isMutableRefObject<T | undefined>
; we'll be able to infer that information directly fromReturnType<typeof useRef<T>>
, allowing us to de-duplicate not only theT | undefined
but also even the entireMutableRefObject<T | undefined>
! You can check this out in action at this playground link. One added bonus of squeezing out that extra bit of type-inference is writing less code and removing a type import.Indeed, the most future-proof (i.e. the most correct) return type would be whatever TypeScript infers it as from
ref.current
(or, more specifically, theusePrevious
function body implementation), but I know some linters require an explicit return type, so I thought i'd include it here.WRT reducing imports, type imports are negligible because they're stripped away during compile time. From the type-only imports release notes: