actualbudget/actual · error
Unhandled action type: ${action.type}
Error message
Unhandled action type: ${action.type} What it means
updateFilterReducer handles only two action types: 'set-op' and 'set-value'. Its default branch throws this when dispatched with any other action type. The `@ts-expect-error` marker shows the action union is not enforced strictly here, so TypeScript cannot prevent invalid actions — making this a runtime-only guard. It protects the filter-conditions UI state machine from unknown messages.
Source
Thrown at packages/desktop-client/src/components/filters/updateFilterReducer.ts:54
// Convert single value to array when switching to oneOf/notOneOf
if (value === null || value === undefined) {
value = [];
} else if (!Array.isArray(value)) {
// @ts-expect-error - fix me
value = [value];
}
}
return { ...state, op: action.op, value };
}
case 'set-value': {
const { value } = makeValue(action.value, {
type: FIELD_TYPES.get(state.field),
});
return { ...state, value };
}
default:
// @ts-expect-error - fix me
throw new Error(`Unhandled action type: ${action.type}`);
}
}
View on GitHub (pinned to d4334cb6e6)
Solutions
- Log action.type in the error to identify the bogus dispatch.
- Correct the dispatch site to send only 'set-op' or 'set-value'.
- If a new behavior is needed (e.g. clearing a value), add an explicit case to updateFilterReducer and to the action union.
- Tighten the reducer's action parameter type so TypeScript rejects unknown action types (removing the need for @ts-expect-error).
Example fix
// before
dispatch({ type: 'set-field', field: 'date' } as any);
// after
dispatch({ type: 'set-op', op: 'is' });
dispatch({ type: 'set-value', value: '2024-01-01' }); Defensive patterns
Strategy: type-guard
Validate before calling
type FilterAction = { type: 'set-op'; op: RuleConditionEntity['op'] } | { type: 'set-value'; value: unknown };
if (action.type !== 'set-op' && action.type !== 'set-value') return state; Type guard
function isFilterAction(a: unknown): a is FilterAction {
return (
typeof a === 'object' && a !== null &&
((a as FilterAction).type === 'set-op' || (a as FilterAction).type === 'set-value')
);
} Try / catch
try {
dispatch(action);
} catch (err) {
if (String(err).startsWith('Unhandled action type')) {
logger.error('Invalid filter action dispatched', action);
return;
}
throw err;
} Prevention
- Give updateFilterReducer a discriminated-union action parameter and remove @ts-expect-error so bad dispatches fail at compile time.
- Centralize filter action creators and only dispatch via them.
- When extending the filter UI, update the action union and reducer cases together.
- Review copy-pasted dispatch calls from other reducers for action-name mismatches.
When it happens
Trigger: Dispatching an action other than 'set-op'/'set-value' into a useReducer wired to updateFilterReducer — e.g. `{ type: 'set-field' }`, a typo like 'setop', or reusing this reducer for another filter feature with different action names.
Common situations: Copy-pasting dispatch calls from another filter reducer with a richer action set; renaming action types at one call site but not the dispatch site; extending the filter UI with new actions and forgetting to add reducer cases.
Related errors
- Unknown display type: ${String(type satisfies never)}
- Unknown template type: ${String(type satisfies undefined)}
- Unknown display type: ${String(visualType satisfies never)}
- Unrecognised Link variant.
- InitialFocus expects a single valid React element as its chi
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/baacb422b6fa9dc5.
Report an issue: GitHub.