our redux-saga generator contains multiple yield statements, that return different results. I need help typing them properly.
Here's an example:
const addBusiness = function* addBusiness(action: AddBusinessActionReturnType): Generator<
Promise<Business> | SelectEffect | PutEffect<{ payload?: ActionPayload<Array<Business>>; type: string }>,
void,
Business | BusinessesContainer
> {
const { url, showToast = true } = action.payload;
const businessDetails: Business = yield Network.get<Business>( // ts error: Type 'Business | BusinessesContainer' is not assignable to type 'Business'.
`businesses?url=${url}`,
);
if (showToast) {
const getBusinessesFromState = (state: AppState) => ({
...state.business.businesses,
});
const businesses: BusinessesContainer = yield select(getBusinessesFromState); // ts error: Type 'Business | BusinessesContainer' is not assignable to type 'BusinessesContainer'
onAddBusinessSuccessToast(businesses, businessDetails);
}
yield put({ // ts error: Type 'SimpleEffect<"PUT", PutEffectDescriptor<{ type: string; payload: Business[]; }>>' is not assignable to type 'SelectEffect'
type: constants.SAVE_BUSINESS_REQUEST,
payload: [businessDetails],
});
In the comments above you see the ts errors that we get. Any help would be appreciated. Thanks
It seems like you are definitely guilty of some over-typing here. Certainly yield Network.get<Business>()
returns a Business
, right? You shouldn't have to write const businessDetails: Business
if the value that you are assigning to it is a Business
.
These generator return types can be really hard to type properly, but Typescript is smart enough to figure out the proper type. You could leave it off entirely and it would be fine. If you've having a hard time figuring out the correct type you can delete the return type temporarily to see what the inferred type is. That's how I resolved your first issue, which is that that the union Business | BusinessesContainer
needs to be changed to an intersection Business & BusinessesContainer
.
I don't know what the ActionPayload
type is but it seems unnecessary. You are typing the payload of your put
action as ActionPayload<Array<Business>>
, but Array<Business>
is correct on its own. There's no reason for the payload
to be optional when you are always providing it.
Those two changes solve your typescript issues...for now. I usually see API calls executed inside a call
effect, which would make the types even more complex. But I'm not sure if the call
is actually required.
const addBusiness = function* addBusiness(action: AddBusinessActionReturnType): Generator<
Promise<Business> | SelectEffect | PutEffect<{ payload: Array<Business>; type: string }>,
void,
Business & BusinessesContainer
> {
const { url, showToast = true } = action.payload;
const businessDetails: Business = yield Network.get<Business>( `businesses?url=${url}` );
if (showToast) {
const getBusinessesFromState = (state: AppState) => ({
...state.business.businesses,
});
const businesses: BusinessesContainer = yield select(getBusinessesFromState);
onAddBusinessSuccessToast(businesses, businessDetails);
}
yield put({
type: "SAVE_BUSINESS_REQUEST",
payload: [businessDetails],
});
}
// dummy types that I filled in to check types
type Business = {
name: string;
id: number;
}
type BusinessesContainer = {
businesses: Business[];
}
const Network = {
get: async <T>(url: string): Promise<T> => {
return {} as unknown as T;
}
}
type AddBusinessActionReturnType = {
type: string;
payload: {
showToast?: boolean;
url: string;
}
}
type AppState = {
business: {
businesses: BusinessesContainer;
}
}
const onAddBusinessSuccessToast = (businesses: BusinessesContainer, businessDetails: Business) => undefined;