Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot yield* a SagaIterator in TS 3.6+ #2079

Open
angelsl opened this issue Jun 9, 2020 · 3 comments
Open

Cannot yield* a SagaIterator in TS 3.6+ #2079

angelsl opened this issue Jun 9, 2020 · 3 comments

Comments

@angelsl
Copy link

angelsl commented Jun 9, 2020

The documentation suggests that we can yield* a Saga to compose them.

However, this isn't possible in TS 3.6+ if the yielded Saga is annotated as SagaIterator: https://codesandbox.io/s/dry-forest-gni5e

import { Action } from "redux";
import { SagaIterator } from "redux-saga";
import { takeEvery, call, put } from "redux-saga/effects";

const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";
const LOG = "LOG";

export function reducer(state = 0, action: Action) {
  switch (action.type) {
    case INCREMENT:
      return state - 1;
    case DECREMENT:
      return state + 1;
    default:
      return state;
  }
}

export function* watchSaga(): SagaIterator {
  yield takeEvery(INCREMENT, function*(action: Action<typeof INCREMENT>) {
    yield put({ type: LOG, message: "Increment!" });
  });

  yield takeEvery(LOG, function*(action: Action<typeof LOG>) {
    yield call(console.log.bind(console), (action as any).message);
  });
}

export function* rootSaga(): SagaIterator {
  yield* watchSaga();
}

This is the type error:

Type 'SagaIterator<any>' must have a '[Symbol.iterator]()' method that returns an iterator.

For TS <= 3.5, this type definition is used, which makes it possible:

export type SagaIterator = IterableIterator<StrictEffect>

But for TS >= 3.6, this is used instead, which is no longer Iterable:

export type SagaIterator<RT = any> = Iterator<StrictEffect, RT, any>

One can do this instead:

export function* rootSaga(): SagaIterator {
  yield fork(watchSaga);
}

But it is not strictly the same.

Is the type definition wrong, or is the documentation wrong, or is this just a limitation due to the stricter generator checking in TS 3.6?

@Andarist
Copy link
Member

Andarist commented Jun 9, 2020

@gilbsgilbs could you check this out?

@angelsl keep in mind that the documentation is not written with TS in mind, so differences between what is possible when using plain JS and what is possible by default when using TS are somewhat expected. For instance, it's possible to just yield anything - for example a number, but types shouldn't quite allow this because it doesn't make much sense. There is little to no gain in using yield* over yield fork() and in general the latter is recommended.

@zachkirsch
Copy link

+1 on this

I get that the documentation is not written for TS in mind, but I still think this is an error in the typings. For what it's worth, I just ran into a case where I needed to use yield* in a saga.

@TomasRejent
Copy link

I have same problem. As a temporary solution I have created this type which use Generator from lib.es2015.generator.d.ts.
type ExpandableSaga<Args extends any[] = any[], R = void> = (...args: Args) => Generator<StrictEffect, R, any>;

It is combination of Saga and SagaIterator types. In most cases my saga does not return any value, that is why return type is set to void by default.

Generator type contains [Symbol.iterator](): Generator<T, TReturn, TNext>; so it can be used with yield*.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants