All Projects → goshacmd → redux-typed-saga

goshacmd / redux-typed-saga

Licence: other
A type-safe alternative to redux-saga. // Using `yield*` in `finally` is currently broken in Babel / ES spec, as it will terminate the generator completely.

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to redux-typed-saga

redux-saga-callback
redux-saga helper functions to await dispatched actions
Stars: ✭ 19 (+58.33%)
Mutual labels:  redux-saga
ts-ui
Telar Social Network using Reactjs
Stars: ✭ 35 (+191.67%)
Mutual labels:  redux-saga
toutiao
模仿今日头条,实现 APP 端,Server 端, Web 管理端
Stars: ✭ 17 (+41.67%)
Mutual labels:  redux-saga
dont-waste-your-ducking-time
🐓 An opinionated guide on how to test Redux ducks
Stars: ✭ 28 (+133.33%)
Mutual labels:  redux-saga
media-library
An online media library application with React, Redux and redux-saga
Stars: ✭ 27 (+125%)
Mutual labels:  redux-saga
react-boilerplate-ssr
ssr react boilerplate
Stars: ✭ 25 (+108.33%)
Mutual labels:  redux-saga
fractal-component
A javascript library that can help you to encapsulate decoupled UI component easily
Stars: ✭ 13 (+8.33%)
Mutual labels:  redux-saga
rocketshoes-react-native
NetShoes Clone with React Native and Redux
Stars: ✭ 38 (+216.67%)
Mutual labels:  redux-saga
next-ifood
Ifood clone made with NextJS ⚛️
Stars: ✭ 68 (+466.67%)
Mutual labels:  redux-saga
react-native-boilerplate
🚀 A highly scalable, react-native boilerplate reinforced with react-boilerplate which focus on performance and best practices. 🔥. 💻 🚀 😎 👾 👽
Stars: ✭ 82 (+583.33%)
Mutual labels:  redux-saga
spotify-release-list
📅 Display list of Spotify releases from artists you follow
Stars: ✭ 142 (+1083.33%)
Mutual labels:  redux-saga
Project06-A-Slack
팀 협업도구, 우리동네 슬랙 🚀
Stars: ✭ 14 (+16.67%)
Mutual labels:  redux-saga
react admin
🎉 TS+Hooks 后台管理系统 http://hooks.sunhang.top
Stars: ✭ 39 (+225%)
Mutual labels:  redux-saga
nextjs-ts-antd-redux-storybook-starter
🏃🏼 Next.js + TypeScript + Ant Design + Redux Toolkit + Redux Saga + Styled Components + Jest + Storybook 企业级项目脚手架模板
Stars: ✭ 78 (+550%)
Mutual labels:  redux-saga
rapid-react
A light weight interactive CLI Automation Tool 🛠️ for rapid scaffolding of tailored React apps with Create React App under the hood.
Stars: ✭ 73 (+508.33%)
Mutual labels:  redux-saga
react-truffle-metamask
Build an DApp using react, redux, saga, truffle, metamask
Stars: ✭ 25 (+108.33%)
Mutual labels:  redux-saga
pokedex
Aplicação desenvolvida com a Pokedéx API
Stars: ✭ 33 (+175%)
Mutual labels:  redux-saga
redux-saga-example
a example to use redux-saga as the middleware of redux
Stars: ✭ 66 (+450%)
Mutual labels:  redux-saga
react-kit
Ready-to-go react App
Stars: ✭ 25 (+108.33%)
Mutual labels:  redux-saga
saga-action-creator
An opinionated lib to create actions for Redux-saga
Stars: ✭ 18 (+50%)
Mutual labels:  redux-saga

😈 redux-typed-saga

Redux-typed-saga is an alternative, well-typed take on the awesome redux-saga.

The inspiration for typing side effects came from Redux Ship. However, Redux Ship has a totally different paradigm, and I don't want to buy into that completely.

This 🚧 experimental project aims to rethink redux-saga with types as first-class citizens.

function* saga(): Saga<Effect, Action, State, void> {
  const num = yield* select(x => x + 10);
  yield* put(set(50));
  const action = yield* take(x => x.type === 'SET' ? x : null);

  // effects are fully user-defined
  yield* Effects.wait(1);
  const response = yield* Effects.httpRequest({ url: 'http://example.com' });
}

Installation and usage

npm install --save redux-typed-saga@https://github.com/goshakkk/redux-typed-saga.git
import { createSagaMiddleware, select, put, take } from 'redux-typed-saga';
import type { Saga, SagaMiddleware } from 'redux-typed-saga';

function* saga(): Saga<Effect, Action, State, void> {
  ...
}

const sagaMiddleware: SagaMiddleware<State, Action> = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));

sagaMiddleware.run(runEffect, saga());

The type

The sagas will generally have a return type of Saga<Effect, Action, State, any> where:

  • Effect is the type of possible side effects, similar to Redux Ship;
  • Action is the type of Redux actions;
  • State is the type of Redux state;
  • any is the return type of the saga. Top-level sagas don't usually have a return type, so any or void it is.

The type of middleware is pretty self-explanatory: SagaMiddleware<State, Action>.

Commands

Commands are called using yield*, as opposed to yield in redux-saga. The reason for this is: typing.

yields are painful to type, therefore there are properly typed wrappers for commands.

  • put<Effect, Action, State>(action: Action): Saga<Effect, Action, State, void>

    Works exactly as in redux-saga.

  • select<Effect, Action, State, A>(selector: (state: State) => A): Saga<Effect, Action, State, A>

    Again, just like in redux-saga, it accepts a selector of type State => A, and returns A.

  • take<Effect, Action, State, A>(actionMatcher: (action: Action) => ?A): Saga<Effect, Action, State, A>

    This one is different, however. The take('ACTION_TYPE') syntax is impossible to type correctly, and returning Action isn't nice.

    To work around that, take instead accepts a matcher function, that takes in an Action, and maybe returns some type A, which is usually:

    1. a type of single action, or
    2. a disjoint union type, if you're matching several actions

    If null is returned, the action is not matched, and we're waiting for other actions.

    There actually are two common uses for take in redux-saga:

    • take('SOME_ACTION_TYPE'). Its counterpart in redux-typed-saga is take(x => x.type === 'SOME_ACTION_TYPE' ? x : null), and the return type will be an object type for that action.
    • take(['ACTION1', 'ACTION2']). Its counterpart in redux-typed-saga is take(x => x.type === 'ACTION1' || x.type === 'ACTION2' ? x : null), and the return type will be a disjoint union of these two action types.

    It is a bit more verbose, but in return, it makes your project easier to type correctly.

  • call<Effect, Action, State, A>(effect: Effect): Saga<Effect, Action, State, any>

    This one is different from the one provided by redux-saga.

    Inspired by Redux Ship, the call command allows for evalution of arbitrary side effects.

    To actually apply the effect, redux-typed-saga will call the runEffect function that you have to pass to sagaMiddleware.run(runEffect, saga). The runEffect function has a type of (effect: Effect) => Promise<any>.

  • spawn<Effect, Action, State>(saga: Saga<Effect, Action, State, any>): Saga<Saga, Effect, Action, State, TaskId>

    Spawn a saga (task) from within a saga, just like in redux-saga, creates a detached fork. This is similar to calling sagaMiddleware.run.

  • kill<Effect, Action, State>(saga: Saga<Effect, Action, State, any>): Saga<Saga, Effect, Action, State, TaskId>

    Kill a previously spawned saga (task).

  • isDying<Effect, Action, State>(): Saga<Effect, Action, State, bool>

    🚧 Detect whether a saga is being killed. This will typically be needed in finally to clean up after the saga. However, using yield* in finally is currently broken in Babel / ES spec, as it will terminate the generator completely.

Pending

Note this is an early 🚧 prototype. It doesn't really support Redux-saga's process paradigm yet, for one.

Example

type State = number;
type Action = { type: 'INC' } | { type: 'DEC' } | { type: 'SET', value: number };

// SAGAS
import { select, put, take } from 'redux-typed-saga';
import type { Saga } from 'redux-typed-saga';

function* saga(): Saga<Effect, Action, State, void> {
  const num = yield* select(x => x + 10);
  console.log('+10=', num);
  yield* put(set(50));
  const action = yield* take(x => x.type === 'SET' ? x : null);
  console.log('set', action.value);

  console.log('waiting one sec');
  yield* wait(1);
  console.log('one sec passed');
}

// EFFECTS
type Effect =
  { type: 'wait', secs: number } |
  { type: 'httpRequest', url: string, method: 'GET' | 'POST' | 'PUT', body: ?string };

function runEffect(effect: Effect): Promise<any> {
  switch (effect.type) {
    case 'wait': {
      const { secs } = effect;
      return new Promise((resolve, reject) => {
        setTimeout(() => reject(secs), secs * 1000);
      });
    }
    case 'httpRequest': {
      return fetch(effect.url, {
        method: effect.method,
        body: effect.body,
      }).then(x => x.text());
    }
    default:
      return Promise.resolve();
  }
}

function wait<Action, State>(secs: number): Saga<Effect, Action, State, number> {
  return call({ type: 'wait', secs });
}

function httpRequest<Action, State>(url: string, method: 'GET' | 'POST' | 'PUT' = 'GET', body: ?string): Saga<Effect, Action, State, string> {
  return call({ type: 'httpRequest', url, method, body });
}

// SETUP
import { createSagaMiddleware } from 'redux-typed-saga';
import type { SagaMiddleware } from 'redux-typed-saga';
const sagaMiddleware: SagaMiddleware<State, Action> = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));

sagaMiddleware.run(runEffect, saga());

// REDUCER
function reducer(state: State =  0, action: Action) {
  switch (action.type) {
    case 'INC':
      return state + 1;
    case 'DEC':
      return state - 1;
    case 'SET':
      return action.value;
    default:
      return state;
  }
}

// ACTION CREATORS
const inc = () => ({ type: 'INC' });
const dec = () => ({ type: 'DEC' });
const set = (value: number) => ({ type: 'SET', value });

License

MIT.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].