I followed the recommendation provided by the react docs that suggest how to use a reducer in conjuction with context.
There is a lot of boiler plate just to add 1 action and a reducer that handles the action.
This is what it looks like before my simplified API, which at first glance doesn't look bad for small demo apps, but quickly adds up over time.
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Context, Dispatch, createContext, useReducer } from 'react'
interface ArtistsState {
artists: { id: number; name: string }[]
activeArtistId?: number
}
const initialState: ArtistsState = {
artists: [{ id: 1, name: 'The Betails' }],
activeArtistId: undefined,
}
const ArtistsContext = createContext([initialState, () => {}])
// ACTIONS
function artistClicked(id: number) {
return <const>{
type: 'artistClicked',
payload: id,
}
}
type Action = ReturnType<typeof artistClicked>
function artistsReducer(state: typeof initialState, action: Action) {
switch (action.type) {
case 'artistClicked':
return {
...state,
activeArtistId: action.payload,
}
default:
return state
}
}
// in app component
function App() {
const [state, dispatch] = useReducer(artistsReducer)
return <ArtistsContext.Provider value={[state, dispatch]}>
<!-- other stuff here -->
</ArtistsContext.Provider>
}
// in child component
function MyComponent() {
const [state, dispatch] = useContext(ArtistsContext)
const onClick = useCallback(() => {
dispatch(artistClicked( someIDHere ))
}, [dispatch])
return (
<button onClick={onClick} >click me!</button>
)
}
To reduce that boilerplate, I came up with a simplified API that you can use like this
import { createStore } from './reducerContext.tsx'
export const artistsStore = createStore({
// initial state goes here
artists: [ {id: 1, name: "The Betails"} ],
activeArtistId: null,
}, {
// actions go here. they should return a reducer function
artistClicked(artistId: string) {
return state => ({
...state,
activeArtistId: artistId
})
}
});
// add this to your app at the highest level possible artistsStore.Provider
// then in a child component you can do this:
import { actionStore } from './actionStore';
const { artistClicked } = actionStore.actions;
function MyComponent() {
const [state, dispatch] = useContext(artistsStore.Context)
const onClick = useCallback(() => {
dispatch(artistClicked( someIDHere ))
}, [dispatch])
return (
<button onClick={onClick} >click me!</button>
)
}