redux-toolkit
redux-toolkit copied to clipboard
Type error when trying to use spread syntax to pass matchers to `isAnyOf` or `isAllOf`
I have a big list of thunks that I want to observe in order to power loading indicators in a pseudo-global way and accomplishing this with matchers.
Instead of writing this which is tedious and error-prone:
const interestingThunks = [
thunk1,
thunk2
];
const isLoading = isAnyOf(thunk1.pending, thunk2.pending);
const isNotLoading = isAnyOf(thunk1.fulfilled, thunk2.fulfilled, thunk1.rejected, thunk2.rejected);
...
builder.addMatcher(isLoading, (state) => {
state.loading= true;
});
builder.addMatcher(isNotLoading , (state) => {
state.loading= false;
});
I would prefer to write this:
const interestingThunks = [
thunk1,
thunk2
];
const interestingPendingThunks = interestingThunks.map(thunk => thunk.pending);
const interestingFulfilledThunks = interestingThunks.map(thunk => thunk.fulfilled);
const isLoading = isAnyOf(...interestingPendingThunks );
const isNotLoading = isAnyOf(...interestingFulfilledThunks );
...
builder.addMatcher(isLoading, (state) => {
state.loading= true;
});
builder.addMatcher(isNotLoading , (state) => {
state.loading= false;
});
But I get the following type error when I try that:
A spread argument must either have a tuple type or be passed to a rest parameter.ts(2556)
Which seems to be happening because the type definition is:
export declare function isAnyOf<Matchers extends [Matcher<any>, ...Matcher<any>[]]>(...matchers: Matchers): (action: any) => action is ActionFromMatcher<Matchers[number]>;
There is a rest argument, but the first argument is a matcher. The workaround is to provide the first argument as normal to satisfy the condition and use the rest syntax for the others. This is a bit more verbose and not intuitive. My guess is that the first argument is defined as such so that it isn't possible to try and call isAnyOf without any arguments but maybe there is a way to have our cake and eat it as well.
Workaround:
const interestingThunks = [
thunk1,
thunk2
];
const interestingPendingThunks = interestingThunks.map(thunk => thunk.pending);
const interestingFulfilledThunks = interestingThunks.map(thunk => thunk.fulfilled);
const isLoading = isAnyOf(interestingPendingThunks[0], ...interestingPendingThunks );
const isNotLoading = isAnyOf(interestingFulfilledThunks[0], ...interestingFulfilledThunks );
...
builder.addMatcher(isLoading, (state) => {
state.loading= true;
});
builder.addMatcher(isNotLoading , (state) => {
state.loading= false;
});
Seems worth tweaking, although I don't have time to look into this myself atm.
It'd still be nice to get this tweaked, but it's low priority. Dropping it out of the 1.9 milestone. If anyone wants to tackle it, please let us know!
I'll repost the gist of my opinion here from Twitter, just in case: so, the problem is, current type signature intentionally requires one element, however .map doesn't guarantee you that the result will have one (e.g if none are pending, the array is empty).
So the question is really, whether isAnyOf with no arguments is valid or not. If it IS, then the types just need to make the constraint simplified to <Matchers extends [ ...Matcher<any>[]]>. If not, however, then the types work exactly as intended.
That makes sense. My vote is to make isAnyOf and isAllOf accept zero arguments then. Its an easy check and would fix this issue and make the types easier to read in my opinion.
Merged, will be out in 1.9.2 shortly.