-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Documentation/Recipe Request: Using redux-saga with TypeScript #1286
Comments
@aikoven do you know any good resources about the topic? |
@huntwj The error you see is a result of TypeScript's way of resolving overloaded signatures. Unfortunately, I don't know of a way to get the correct error message here, except for commenting out all the signatures of You may also try the new typings for the 1.0 (#1255), where these two groups of signatures are swapped, so that users are more likely to get the correct error message, since |
@huntwj did you ever find a resolution for this? |
I seem to have "moved past" the problems, though I cannot remember exactly what the solution was. I'm guessing it was related to using the 1.0 typings, but I can't remember off hand and unfortunately don't have time to do the necessary testing right now. What I still think could be useful, and is something I may attempt to put together in the near future, would be a page of documentation providing a quick "getting started" guide for using redux saga with TypeScript. |
as a quick fix I just added a |
I had error So problem was in export function* fetchChart(action: FetchChartAction) { // <-- here added type FetchChartAction
const {error, data} = yield call(fetchChartData, action)
if (!error) {
yield put(fetchChartSuccess(data))
} else {
yield put(fetchChartFailure(error))
}
}
export function* watchFetchChart() {
yield takeLatest(ChartActionTypes.FETCH_CHART, fetchChart) // <-- here pass `action` argument
} |
If above solution didn't work for you (as it didn't for me) try this one #1188 (comment) |
Are there any good examples/codebases that leverage redux-saga typescript types? |
I've been using Redux-Saga and TypeScript at work in a medium-sized front-end codebase for a while. Overall, it works fine and I want to share the main issues, pitfalls and solutions I used. If you find it interesting, it could be a starting point for writing a recipe. SetupI defined a custom action interface that extends Redux's import { Action } from 'redux';
interface AppAction extends Action {
payload?: any;
}
interface LogAction extends AppAction {
type: 'LOG_ACTION';
payload: string;
} Since TypeScript can guarantee there's no other yield RSEffects.takeEvery(
(action: AppAction) => (
action.type === 'LOG_ACTION'
&& action.payload === 'hello'
),
someWorker,
); instead of: yield RSEffects.takeEvery(
(action: Action) => (
action.type === 'LOG_ACTION'
&& (action as LogAction).payload === 'hello'
),
someWorker,
); (there might be more insightful examples though) The store can be instantiated normally : const sagaMiddleware = createSagaMiddleware();
const store = Redux.createStore(
reducer,
Redux.applyMiddleware(sagaMiddleware),
);
sagaMiddleware.run(rootSaga); SagasThe simplest way to create a saga is to create a generator having import { SagaIterator } from '@redux-saga/core';
import { delay } from '@redux-saga/core/effects';
function* saga(): SagaIterator {
yield delay(1000);
console.log('ok');
} Effects can be used as expected: function* saga(): SagaIterator {
const chan = ReduxSaga.channel(); // <== "Channel" type inferred
chan.put('hello');
const res: string = yield RSEffects.take(chan); // <== can take from a channel, no problem
console.log(res);
yield RSEffects.takeEvery(
'LOG_ACTION', // <== can take from an action type string
function* (action: LogAction): SagaIterator {
console.log(action);
},
);
} The import { Saga, SagaIterator } from '@redux-saga/core';
import { call, delay } from '@redux-saga/core/effects';
function* saga(subSaga: Saga): SagaIterator {
yield delay(1000);
yield call(subSaga);
} Return types / Next typesThis is THE major painpoint when working with Redux-Saga and TypeScript: the lack of support for generators return types (this will be fixed in TS 3.6) and not flexible next() typing for generators (cannot find a proper issue to track this). The only workaround I know is to type effects return type explicitly: function* saga(subSaga: Saga): SagaIterator {
const response: Response = yield RSEffects.call(fetch, 'https://httpstat.us/200'); // <== Typed explicitely, otherwise we have "any"
const txt: string = yield RSEffects.call([response, 'text']); // <== Same
console.log(txt);
} Generic types and SagasThis is the less straightforward part I had to deal with. TypeScript seems pretty bad at inferring generic types on generator functions. It may work in simple cases, but very often, TypeScript will report some typing errors when using the Example: function* genericSaga<
T extends (str: string) => number
>(callback: T): SagaIterator {
yield RSEffects.delay(1000);
console.log(callback('hello'));
}
function* saga: SagaIterator {
yield RSEffects.call(genericSaga, 42); // No error! WTF?
} This is an example where TypeScript reports no error at all. Conversely, I had more complex cases that I failed to simplify where TypeScript would be unable to infer a type. And it's really unpleasant to try to give an explicit generic type to When working on this problem, I came across a workaround that I found elegant and I think is worth sharing. The solution was to refactor saga generators into plain effect functions. I found this pattern to be much convenient to use since it hides the function genericEffect<
T extends (str: string) => number
>(callback: T): CallEffect {
return call(function*(): SagaIterator {
yield RSEffects.delay(1000);
console.log(callback('hello'));
});
}
function* saga: SagaIterator {
yield genericEffect(42); // Error: 42 not assignable to Fn. This is expected.
yield genericEffect((str) => str.length); // OK
} Hope this helps. What do you think? Did I miss something important? Can it serve as a base for writing a recipe? Did I make any nasty mistake? If you have any question, do no hesitate. |
This is super helpful, thanks! |
[SOLVED] Hi All, I`m getting an error for following code, just followed examples.
Currently I am doing like this,
Any suggestions ? thanks in advance.!! [UPDATE] : please see the comment below by @gilbsgilbs
|
@nithincvpoyyil this is not a typing / typescript issue. As indicated, you need to find a way to use |
@gilbsgilbs : Thank u for the response. Yes, that was the issue. jest test function should be async, I will add it as a comment above. feeling so stupid.. |
Seems like strong typing of generators is finally gonna come in TS 3.6? microsoft/TypeScript#31639 Which means, we will be able to have well typed sagas using generic types? (Because it's not really possible atm, am I correct?) Are there some plans/work towards supporting it? |
Yes, when TS@3.6 gets released we are definitely going to work on improved typings. |
Not willing to spoil the party or anything, but microsoft/TypeScript#31639 isn't actually sufficient to strongly type sagas and other function* mySaga(): SagaIterator<number> {
yield call(someEffect);
return "not a number"; // This will fail
} and even the function* mySaga(): SagaIterator<void, number> {
const val = yield call(someEffect); // val will have "number" type
} it remains too dumb to properly infer types in such cases: function* mySaga(): SagaIterator {
return "not a number"; // This generator should have `string` ReturnType properly infered
}
function* anotherSaga(): SagaIterator {
const val = yield call(mySaga); // val will still have `any` type :(
} And as far as I understand, there's nothing we can do about it at the moment. I'm looking for the actual issue to follow on TypeScript, but I'm unsure. This isn't really related to this issue though. Maybe we could keep track of this in another issue @Andarist? See microsoft/TypeScript#30790 (comment) and microsoft/TypeScript#2983 (comment) for more information. |
I think one approach to improve the situation a bit would be to write typescript-eslint rules crafted for Saga that would for example ensure that:
And generator return types could probably help to implement this more easily: function* childSaga(): SagaIterator<number> {
yield delay(100);
return 12;
}
function childCallEffect(): CallEffect<number> {
return call(childSaga);
}
// until here, TS 3.6 should be enough to strongly type everything.
// starting from here, the linting rule would come into place
function* parentSaga(): SagaIterator<void> {
// typeof call(childSaga) is CallEffect<number> => ensure we assign number here.
const childSagaResult: number = yield call(childSaga);
// And similarly
const childCallEffectResult: number = yield childCallEffect();
// typeof call(fetch<any>(…)) is CallEffect<Response<any>> => ensure we assign a Response<any> here.
const reqResult: Response<any> = yield call(fetch<any>('http://example.com'));
} I'm very unsure how realistic this idea is in term of complexity or even feasibility. Yet, some typescript-eslint rules are smart enough to leverage type information. Therefore, I believe it's feasible at very least, maybe just not worth the complexity. The thing is I'm quite pessimistic on the ability of the TS team to come up with a general solution to this issue anytime soon. And I don't blame them, the general case seems very very hard to tackle after thinking of it a bit. Still, it may be more worth than we think to provide a very imperfect solution. What do you think? |
If it's possible then it would be great if somebody could work on it - I'm afraid the effort would be community-driven though, as personally I don't have time to explore this right now. |
I made some experiments tonight and it appears to be feasible. I updated redux-saga types to allow specifying a return type to saga generators (using TS 3.6 generator return types) and I am able to get this return type within an ESLint rule 🎉 . I may open a PR sometime for Sagas return types and if it makes it, I may release an ESLint plugin to enforce strong types on effects. We could then write a good Typescript recipe :) . |
Great, looking forward to it. |
@gilbsgilbs any progress on this? |
@dwilt progress on the ESLint rule? I don't think I'll spend any time on it because somebody found a simpler solution that would cope well with type inference (at the price of slight changes in the way sagas are invoked). If our community don't fall into pointless quarrels waged by ego, I think it might be the definitive solution to strongly typed sagas. About the improved typings for redux-saga with TS3.6 (which I think is a prerequisite for all the solutions we have), I made a WIP PR (#1892) however, as I mentioned, I'm stuck and I need to figure out why |
### Description There was a typo in `requesterAddrress` in the code handling QR codes in secure send mode. Not sure exactly of the exact problem it would cause, I'll let @tarikbellamine comment. Long term fix is to find a solution for fully typed `redux-saga`. As of today, there's no clear solution to this, see more discussion redux-saga/redux-saga#1286 ### Other changes - Added manual types for `yield take(Actions...)`. ### Tested TypeScript check succeeds. ### Backwards compatibility Yes.
I’d like to overhaul the docs to emphasize Typescript in the near future. Currently we are blocked by TS itself providing better support for yield overrides. You can track that progress here: microsoft/TypeScript#43632 Going to close this issue for now. |
I see that there are type definitions, and some people have gotten them to work. As someone that's new to TypeScript, however, converting my redux-saga to TypeScript has gone less than smoothly.
It would be nice if we could add a page of documentation (preferably written by someone that has had success in doing this...) outlining the process of converting JS sagas to TS and/or writing TS sagas from scratch.
Just in case my issue turns out to be easily solvable by the experts, I'll include it here:
Specifically, I am attempting to use
takeLatest(...)
and call my API request sagas, but I am getting type errors on those that take parameters.My TypeKeys:
In my watchers generator:
and in my sagas:
Then the error is:
I used to be able to call takeLatest passing in a string as the action type, but things seem to change now that I'm dealing with a TS (string-based) enum.
The text was updated successfully, but these errors were encountered: