Skip to content

Instantly share code, notes, and snippets.

@sarimarton
Last active February 29, 2020 02:38
Show Gist options
  • Save sarimarton/d5d539f8029c01ca1c357aba27139010 to your computer and use it in GitHub Desktop.
Save sarimarton/d5d539f8029c01ca1c357aba27139010 to your computer and use it in GitHub Desktop.
Properly typed React-Redux hooks with exact event shape match in dispatch() calls
import * as RR from 'react-redux'
// Exact matcher. This was made by a genius guy...
// https://github.com/microsoft/TypeScript/issues/12936#issuecomment-524631270
type ExactInner<T> = <D>() => D extends T ? D : D
type Exact<T> = ExactInner<T> & T
export function exact<T>(obj: Exact<T> | T): Exact<T> {
return obj as Exact<T>
}
// This is a short getter for precisely typed hooks. The useSelector part is given
// in the official React-Redux guide. The useDispatch part is the point here. The
// React-Redux guide is not helpful here at all. There is a short version to make it:
// export const useDispatch = () => RR.useDispatch<Dispatch<StoreEvent>>()
// but it doesn't protect against excess properties in the event objects.
// Exact match is hard to do in TS, and the above solution is the only _proper_
// solution which works with unions. It needs an extra runtime invocation though,
// so we wrap our helper here
export function getTypedHooks<Store, StoreEvent>() {
const useSelector: RR.TypedUseSelectorHook<Store> = RR.useSelector
const useDispatch = () => {
const dispatch = RR.useDispatch()
return (event: StoreEvent) => dispatch(exact(event))
}
return { useSelector, useDispatch }
}
// Use it in your store file like this:
//
// import { getTypedHooks } from 'get-typed-redux-hooks'
//
// interface Store { ... }
// type StoreEvent =
// | { type: 'EVENT1' }
// | { type: 'EVENT2', data: any }
//
// export const { useSelector, useDispatch } = getTypedHooks<Store, StoreEvent>()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment