jsdoc-duck v0.0.5
jsdoc-duck
jsdoc-duck is a minimalist library that helps organize mid-complex state handling with type-guarded dispatch-capable actions. It leverages useReducer
and jsDoc/Javascript to bring type safety and structure to your application's state management.
It is based on react-state-factory.
The big difference is for simplicity reason don't included useStateReducer counterpart.
this library is also useful for react/typescript application in same way as react-state-factory.
Installation
npm add jsdoc-duck
TLDR
Just define the actions and state then useDuck give back your typed state and quack with list of actions.
jsDoc/JS example
/**
* @typedef {{ type: "FOO", payload: string[] }
* | { type: "FUU", payload: number }
* } ActionsMap
*/
/** @type {import('/jsdoc-duck').Labels<ActionsMap>} */
const labels = {
FOO: "FOO",
FUU: "FUU",
}
/** @typedef {{foo: string[], fuu: number}} FooState */
/** @type {import('jsdoc-duck').Reducer<ActionsMap, FooState>} */
export const fooReducer = (state, { type, payload }) => {
switch (type) {
case labels.FOO: return {...state, foo: [...foo, payload]}
case labels.FUU: return {...state, fuu: payload}
default: return state;
}
}
/** @type FooState */
export const fooSetup = {
foo: ['this is a real minimal foo application', 'are create tokens'],
fuu: 42
}
export const FooAndFuuComponent = () => {
const [state, quack] = useDuck(fooReducer, fooSetup, labels);
const addToken = () => {
const rnd = Math.random()
const token = rnd.toString(36).toUpperCase().slice(-7);
quack.FOO(token);
quack.FUU(rnd);
};
return (
<pre onClick={addToken}>{JSON.stringify(state, null, 2)}</pre>
)
Use Case - React Poker Game
This example are under development, and there are just paste the relevant code parts,
I would like create a mid-complex application which is a bit complex than casual counter application,
where this small library ( less than 100 LOC ) can show the usability of typed useReducer
First of all I create a AM - ActionsMap which is holding group actions with united typedefs
/** * @typedef {{ type: "SUMMON", payload: Player[] } * | { type: "DECK", payload: [string, string][] } * | { type: "NEW_GAME", payload: Player } * | { type: "BLINDS", payload: number | string } * | { type: "DEALING", payload: null } * | { type: "PRE_FLOP", payload: string } * | { type: "FLOP", payload: string } * | { type: "TURN", payload: number } * | { type: "RIVER", payload: string } * | { type: "SHOWDOWN", payload: string } * | { type: "CALL", payload: string } * | { type: "RAISE", payload: string } * | { type: "FOLD", payload: string } * | { type: "CHECK", payload: string } * | { type: "NEXT_HAND", payload: string } * | { type: "ESCAPE", payload: string } * | { type: "CHAMPION_ARE", payload: string } * | { type: "CALC_RANK", payload: null } * } ActionsMap */
This is a autogenerated actionsMap aka Labels
/** @type {import('/jsdoc-duck').Labels<ActionsMap>} */ const actionsMap = { DECK: "DECK", SUMMON: "SUMMON", NEW_GAME: "NEW_GAME", BLINDS: "BLINDS", DEALING: "DEALING", PRE_FLOP: "PRE_FLOP", FLOP: "FLOP", TURN: "TURN", RIVER: "RIVER", SHOWDOWN: "SHOWDOWN", CALL: "CALL", RAISE: "RAISE", FOLD: "FOLD", CHECK: "CHECK", NEXT_HAND: "NEXT_HAND", ESCAPE: "ESCAPE", CHAMPION_ARE: "CHAMPION_ARE", CALC_RANK: "CALC_RANK" };
State of game is a simple jsDoc @typedef
/** * @typedef {{ * players: Player[], * phase: string, * dealer: Player, * deck: [string, string][], * rate: Rate[], * button: string, * }} TexasHolden */
Reducer cointains the core logic of game
/** @type {import('jsdoc-duck').Reducer<ActionsMap, TexasHolden>} */ export const pokerReducer = (state, { type, payload }) => { switch (type) { case actionsMap.DECK: return {...state, deck: payload }; case actionsMap.SUMMON: return {...state, players: payload }; case actionsMap.NEW_GAME: return {...state, dealer: payload }; case actionsMap.FLOP: return {...state, phase: FLOP, foo: payload }; case actionsMap.CALC_RANK: return {...state, rank: rankCalc(state.players, state.dealer)} case actionsMap.DEALING: { const {deck} = state; return { ...state, phase: actionsMap.DEALING, players: state.players.map(player => ({...player, hand: [...player.hand, deck.shift()] })), deck } } default: return state; } }
Finally the Poker component with
useDuck
a dispatchable actions list.export const Poker = () => { // poker, and quack of course in array destruction, so you can give any name which is fit for your component. const [poker, quack] = useDuck(pokerReducer, pokerSetup, actionsMap);
useEffect(() => { const deck = aRandomDeck(); quack.DECK(deck); quack.SUMMON( "Andy|Boris|Medar|Oriana".split("|").map(name => ({ name, hand: [], coin: 1000 })) ); quack.BLINDS(0); quack.NEW_GAME({ name: '- dealer -', hand: [], coin: 0 }); quack.DEALING(); quack.DEALING(); quack.CALC_RANK(); }, []);
return (
<pre>{JSON.stringify(poker, null, 2)}</pre>
)