I have a React hook that requests a remote API and returns something. By default, while the API is being fetch, the returned value is undefined
.
Still, I introduced a new prop that allows returning a default value – so, while the API is fetch, the default value is returned.
Is it possible for TypeScript to infer that when defaultValue
is defined, then the return can not be undefined
?
This is my code so far:
interface UseMyHookOptions<T> {
defaultValue?: T
}
type UseMyHookReturn<T> = T extends undefined
? [T | undefined, (data: T) => void]
: [T, (data: T) => void]
function useMyHook<T>(key: string, options: UseMyHookOptions<T> = {}): UseMyHookReturn<T>{
const defaultValue = options?.defaultValue
const remoteValue = useFetch<T>(`/api/${key}`)
const setValue = (value: T) => { console.log(value) }
const value = remoteValue ?? defaultValue;
return [value, setValue] as UseMyHookReturn<T>;
}
Expected examples:
// `foo` type: expected: T (because defaultValue is defined)
// got: T
const [foo, setFoo] = useMyHook<string>('foo', { defaultValue: 'something' });
// `bar` type: expected: T | undefined (because defaultValue is not defined)
// got: T
const [bar, setBar] = useMyHook<string>('bar');
What you need here is function overloading to create multiple signatures depending on what parameters are provided.
How about something like this?
interface UseMyHookOptions<T> {
defaultValue?: T
}
type UseMyHookReturn<T> = [T, (value: T) => void]
function useMyHook<T>(key: string): UseMyHookReturn<T | undefined>;
function useMyHook<T>(key: string, options: UseMyHookOptions<T> & { defaultValue: T }): UseMyHookReturn<T>;
function useMyHook<T>(key: string, options: UseMyHookOptions<T> & { defaultValue?: undefined }): UseMyHookReturn<T | undefined>;
function useMyHook<T>(key: string, options: UseMyHookOptions<T> = {}) {
const defaultValue = options?.defaultValue
const remoteValue = useFetch<T>(`/api/${key}`)
const value = remoteValue ?? defaultValue;
const setValue = (value: T) => { console.log(value) }
return [value, setValue];
}
Here you'd be defining three overloads for your useMyHook
function that changes what is returned depending on whether options
(and more specifically if options.defaultValue
) are provided.
This way, you'd have:
const [foo, setFoo] = useMyHook<string>('foo', { defaultValue: 'something' }); // foo is a string
const [bar, setBar] = useMyHook<string>('bar'); // bar is a string | undefined
const [baz, setBaz] = useMyHook<string>('baz', {}); // baz is a string | undefined (no defaultValue provided despite options given).
You can check this playground in case you want to tinker with this a bit further.