typescript-fsa-reducers
typescript-fsa-reducers copied to clipboard
Interfere return type in .case doesn't work
Hey, I just tried to use the library but unfortunately there is no type check in .case method for return value.
If I implement my reducer like this:
const execute = (execute: Execute, action: ReduxAction): Execute => {
if (isType(action, actions.calibrationSettingsDone)) return { ...execute, settings: action.payload.result };
...
it will not allow me to return settings1 property because it doesn't exist in Execute model and it's fine however in your library it's completely fine to return any object.
You're right. Unfortunately this is a limitation of TypeScript that I don't see a way to work around in this circumstance. Unknown properties on object literals are not checked in the return value of functions which are given as arguments because TypeScript thinks its fine to accept a function which produces a return type which is a subtype of the desired type. A demo of this behavior:
interface HasX {
x: number;
}
function getHasX(): HasX {
// Extra properties are errors.
return { x: 1, y: 2 };
}
function callGetHasX(f: () => HasX) {
f();
}
// No error here.
callGetHasX(() => ({ x: 1, y: 2 }));
If this failure is enough of problem for you to not use this library, I understand. However, here are a few workarounds:
-
Use a variant of object spread from a library with a more restrictive signature.
In my code, I use
pureAssignfrom the pure-assign library instead of object spread orObject.assign. Its signature offunction pureAssign<T>(baseObject: T, ...updates: Array<Partial<T>>): Tprevents unknown properties from being added which are not in the base type. Not to say you have to use this library in particular, but using a version of object assignment with more restrictive typing can be a generally good idea when writing TypeScript code.
-
Pull your handlers out into separate functions.
You can choose to write your code as
const reducer = reducerWithoutInitialState<Execute>() .case(actions.calibrationSettingsDone, handleCalibrationSettingsDone); const handleCalibrationSettingsDone = (execute: Execute, payload: CalibrationSettingsDonePayload): Execute => ({ ...execute, settings: payload.result });Some people prefer this style anyways because it makes the reducer look less cluttered, although it does require more typing.
I'll think about this for a bit to see if I can come up with a better solution. If not, I'll make a note in the README about this.