useReducer in TypeScript
Here's how to type React.useReducer in TypeScript.
import React from 'react'
/* 1. Create a union type for the actions that you have */
type Action = SearchAction | ToggleUserAction
interface User {
id: string
}
/* 2. You can differentiate actions based on type string */
interface SearchAction {
type: 'search'
term: string
}
interface ToggleUserAction {
type: 'toggle'
id: string
}
/* 3. Define your state with all the bits that need to relate to each other */
interface State {
term: string | undefined
users: User[]
filteredIds: string[]
selectedIds: string[]
}
/* 4. Define your reducer using the interfaces you've just declared */
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'search':
return {
...state,
term: action.term,
filteredIds: filterIds(state.users, action.term), // offscreen
}
case 'toggle':
return {
...state,
selectedIds:
action.id === 'all'
? state.selectedIds.length === state.users.length
? []
: state.users.map((user) => user.id)
: state.selectedIds.includes(action.id)
? state.selectedIds.filter((id) => id !== action.id)
: [...state.selectedIds, action.id],
}
default:
return state
}
}
function UserCombobox(props: {
label: string
name: string
users: User[]
defaultValue: string[]
}) {
/* 5. The reducer requires the action, even if just this union type, to be an array */
/* 6. Feed it initial state, matching the State interface */
const [state, dispatch] = React.useReducer<State, [Action]>(reducer, {
term: '',
users: props.users,
filteredIds: [],
selectedIds: props.defaultValue ?? [],
})
/* 7. One of the event handlers and call to dispatch */
function handleSearch(evt: React.ChangeEvent<HTMLInputElement>) {
const term = evt.target.value ?? ''
dispatch({ type: 'search', term })
}
/* 8. Some use of current state from the reducer */
return (
<input
type="search"
name="term"
onChange={handleSearch}
value={state.term}
/>
)
}
How do you like them apples?