const initialState = {
image: null,
filters: {
brightness: 0,
contrast: 1,
saturation: 0,
grayscale: false,
invert: false,
},
layers: [],
activeTool: 'select',
undoStack: [],
redoStack: [],
zoom: 1,
panOffset: { x: 0, y: 0 },
isProcessing: false,
error: null,
};
function editorReducer(state, action) {
switch (action.type) {
case 'LOAD_IMAGE':
return {
...state,
image: action.payload,
filters: { ...initialState.filters },
undoStack: [],
redoStack: [],
};
case 'SET_FILTER':
return {
...state,
undoStack: [
...state.undoStack, state.filters
],
redoStack: [],
filters: {
...state.filters,
[action.payload.name]:
action.payload.value
},
};
case 'UNDO': {
if (state.undoStack.length === 0)
return state;
const previous = state.undoStack[
state.undoStack.length - 1
];
return {
...state,
filters: previous,
undoStack: state.undoStack.slice(0, -1),
redoStack: [
...state.redoStack, state.filters
],
};
}
case 'REDO': {
if (state.redoStack.length === 0)
return state;
const next = state.redoStack[
state.redoStack.length - 1
];
return {
...state,
filters: next,
redoStack: state.redoStack.slice(0, -1),
undoStack: [
...state.undoStack, state.filters
],
};
}
case 'SELECT_TOOL':
return {
...state, activeTool: action.payload
};
case 'SET_ZOOM':
return { ...state, zoom: action.payload };
case 'SET_PROCESSING':
return {
...state, isProcessing: action.payload
};
case 'SET_ERROR':
return { ...state, error: action.payload };
case 'RESET_ALL':
return {
...initialState, image: state.image
};
default:
throw new Error(
`Unknown action: ${action.type}`
);
}
}
function App() {
const [state, dispatch] = useReducer(
editorReducer, initialState
);
return (
<EditorContext.Provider
value={{ state, dispatch }}>
<Toolbar
activeTool={state.activeTool}
onToolSelect={(tool) => dispatch({
type: 'SELECT_TOOL', payload: tool
})}
/>
<CanvasArea
image={state.image}
filters={state.filters}
zoom={state.zoom}
/>
<SettingsPanel
filters={state.filters}
onFilterChange={(name, value) =>
dispatch({
type: 'SET_FILTER',
payload: { name, value }
})
}
/>
</EditorContext.Provider>
);
}
The reducer captures complete filter state atomically. No more partial undo restores.
Test: rapid slider movements, undo/redo sequences, concurrent actions — all transitions predictable.
git switch -c refactor/PIXELCRAFT-038-reducer
git add src/
git commit -m "Centralize state with useReducer, fix undo bug (PIXELCRAFT-038)"
git push origin refactor/PIXELCRAFT-038-reducer
# PR → Review → Merge → Close ticket ✅
State machines and finite automata.
Each dispatch is a state transition. The reducer makes transitions explicit, predictable, and testable.