A reducer takes current state and an action describing a requested change, then returns the next state. In React, useReducer connects that transition function to component state. Discriminated-union actions make operations explicit; generic reducers let the same state logic work across different element types. section 6.1.5 section 6.5
Reordering a playlist
A generic reorder reducer accepts an array and actions such as moveUp or moveToPosition. A useReorderable hook wraps it with useReducer and exposes operations to a playlist component. TypeScript may need an explicit Reducer<State<T>, Action> argument when useReducer loses the generic type; React 19 removes that workaround. section 6.1.5
Listing 6.2 The useReorderable hook
import { useReducer } from "react";
type State<T> = T[];
type Action =
| { type: "moveUp"; index: number }
| { type: "moveDown"; index: number }
| { type: "moveToTop"; index: number }
| { type: "moveToBottom"; index: number }
| { type: "moveToPosition"; index: number; targetIndex: number };
function reorder<T>(state: State<T>, action: Action): State<T> {
const newState: T[] = [...state];
switch (action.type) {
case "moveUp": {
if (action.index <= 0 || action.index >= newState.length) {
return state;
}
// Swap previous element with index
[newState[action.index - 1], newState[action.index]] = [
newState[action.index],
newState[action.index - 1],
];
return newState;
}
case "moveDown": {
if (action.index < 0 || action.index >= newState.length - 1) {
return state;
}
// Swap next element with index
[newState[action.index], newState[action.index + 1]] = [
newState[action.index + 1],
newState[action.index],
];
return newState;
}
case "moveToTop": {
if (action.index <= 0 || action.index >= newState.length) {
return state;
}
// Move index to top
const itemToTop = newState.splice(action.index, 1)[0];
newState.unshift(itemToTop);
return newState;
}
case "moveToBottom": {
if (action.index < 0 || action.index >= newState.length) {
return state;
}
// Move index to bottom
const itemToBottom = newState.splice(action.index, 1)[0];
newState.push(itemToBottom);
return newState;
}
default:
return state;
}
}
export function useReorderable<T>(initial: State<T>) {
const [state, dispatch] = useReducer<
Reducer<State<T>, Action>
>(reorder, initial);
return {
list: state,
moveUp: (index: number) => #1
dispatch({ type: "moveUp", index }), #1
moveDown: (index: number) => #1
dispatch({ type: "moveDown", index }), #1
moveToTop: (index: number) => #1
dispatch({ type: "moveToTop", index }), #1
moveToBottom: (index: number) => #1
dispatch({ type: "moveToBottom", index }), #1
};
}
Inspect the complete hook implementation, including its action union and array-reordering cases. section 6.1.5
Reducers also organize shared application data: a provider can expose state and callbacks through context while reducer actions update a combined state object. One implementation wraps reducer cases with Immer and persists the state in local storage. section 8.4.2








