chan.dev/ lessons/ epicreact

State Reducer, solution

Add a state reducer option to the useToggle hook

This lesson is part of the epicreact.dev’s Advanced React Patterns module

OK! Let’s invert control of our useToggle hook. We only need to make one major change to our implementation. So,

toggle.tsx
export function useToggle({ initialOn = false, reducer } = {}) {
const { current: initialState } = React.useRef<ToggleState>({
on: initialOn,
});
const [state, dispatch] = React.useReducer(
toggleReducerreducer,
initialState
);
/* ...rest of implementation... */
}

Unfortunately, requiring a reducer function (as an argument) has the undesirable effect of breaking it everywhere that a reducer isn’t defined.

Run code and highlight reducer is not a function error.

TODO

Assign a default state reducer to retain existing behavior

We can retain the previous experience with a default value assignment syntax.

toggle.tsx
export function useToggle({
initialOn = false,
reducer = toggleReducer,
} = {}) {
/* ...rest of implementation... */
}

TODO

Provide a custom state reducer at the the call site

Now we can override the default toggleReducer — by providing a reducer option. Let’s provide a state reducer function that dispables the toggle when we’ve hit our click limit.

app.tsx
const { on, getTogglerProps, getResetterProps } = useToggle({
reducer(state: ToggleState, action: ToggleAction) {
switch (action.type) {
case "toggle": {
return { on: !state.on };
}
case "reset": {
return action.initialState;
}
}
},
});

With our override state reducer in place, add the additional toggle logic.

app.tsx
const { on, getTogglerProps, getResetterProps } = useToggle({
reducer: function toggleReducer(
state: ToggleState,
action: ToggleAction
) {
switch (action.type) {
case "toggle": {
if (clickedTooMuch) {
return state;
}
return { on: !state.on };
}
case "reset": {
return action.initialState;
}
}
},
});

What we did

We’ve made it possible to override the default behavior of our useToggle hook, at the call site. This lets others extend the hook’s capabilities without having to modify the original code!

And by using the keeping the previous implementation as default, we’ve protecting exitisting code from a breaking change.

Resolving the TypeScript errors

All that’s left is to fix the TypeScript errors.

[Marker: TypeScript]

toggle.tsx
import {
ToggleAction,
ToggleState,
useToggle,
} from "./toggle.tsx";
/* ...rest of implementation... */