Giter Site home page Giter Site logo

tomekkleszcz / redux-saga-promise-actions Goto Github PK

View Code? Open in Web Editor NEW
8.0 1.0 2.0 161 KB

Simple to use library which allows to return promise after an action is dispatched.

License: MIT License

TypeScript 96.19% JavaScript 3.81%
promise-action redux-saga promise-middleware redux-store

redux-saga-promise-actions's Introduction

๐Ÿ› ๏ธ redux-saga-promise-actions ๐Ÿ“ฆ

npm npm GitHub Workflow Status GitHub Workflow Status Coverage Status

Simple to use library which allows to return promise after an action is dispatched.

๐Ÿ“ฅ Installation

npm install redux-saga-promise-actions

or

yarn add redux-saga-promise-actions

๐Ÿค” Why this library does even exists?

In most of my projects I use Formik for handling the forms and Redux saga for handling async tasks. Unfortunately it is not possible to control Formik state from saga, so there is a need to return the information if the async task was handled successfully or not to the component.

One of the possible solutions is to store the information if the form is submitting and its errors in Redux store. However it is not recommended to store the UI state in Redux store. If I would store the submitting and errors in the Redux store why would I use Formik?

The second possible solution is to pass the Formik helpers (i.e. setSubmitting or setErrors) as an action props. Or... You can use this library instead. ๐Ÿ™‚

๐Ÿ’ˆ Example

Please check out example directory.

๐Ÿงฐ Usage

Include promise middleware

First of all, you have to add the promise middleware to your Redux store middleware chain.

๐Ÿšจ The promise middleware should be before the saga middleware in the chain.

//Redux store
import {createStore, applyMiddleware} from 'redux';

//Middlewares
import {promiseMiddleware} from 'redux-saga-promise-actions';
import createSagaMiddleware from 'redux-saga';

const sagaMiddleware = createSagaMiddleware();

const store = createStore(rootReducer, {}, applyMiddleware(promiseMiddleware, sagaMiddleware));

Declare promise actions

Then, you can declare the promise action. The library uses typesafe-actions under the hood so declaring actions is easy as it can be.

Javascript

//Action creators
import {createPromiseAction} from 'redux-saga-promise-actions';

const signUp = createPromiseAction('SIGN_UP')();

Typescript

//Action creators
import {createPromiseAction} from 'redux-saga-promise-actions';

const signUp = createPromiseAction('SIGN_UP')<
    {email: string, password: string},
    {accessToken: string, refreshToken: string},
    {email: string | null, password: string | null}
>();

When createPromiseAction is called, three actions are created under the hood (in this case: SIGN_UP_REQUEST, SIGN_UP_SUCCESS, and SIGN_UP_FAILURE). If you do not like this action type naming convention, there is an escape catch and you can name the action types as you want.

//Action creators
import {createPromiseAction} from 'redux-saga-promise-actions';

const signUp = createPromiseAction(
    'SIGN_UP_REQUEST',
    'SIGN_UP_SUCCESS',
    'SIGN_UP_FAILURE'
)<
    {email: string, password: string},
    {accessToken: string, refreshToken: string},
    {email: string | null, password: string | null}
>();

These two examples have identical promise action, but in the second one you have full control over how the action types are named.

Handle actions

Finally, you can handle promise actions in your code.

Component

dispatch(signUp.request({email: '[email protected]', password: 'TestPassword'}))
    .then(() => alert('Success!'))
    .catch(err => console.error(err));

Saga

There are two ways to handle promise actions in your sagas.

//Promise actions
import {takeEveryPromiseAction} from 'redux-saga-promise-actions/effects';

function* signUp(action) {
    return yield axios.request(...);
}

export authSaga = [
    takeEveryPromiseAction(actions.signUp, signUp)
];

The takeEveryPromiseAction works just like takeEvery effect creator from redux-saga, but it wraps the saga in try catch. It accepts the promise action as the first argument and the saga as a second one. After the saga is completed it resolves promise action and dispatch the success action with saga return value. If any error occur (eg. the requests fails) the promise action is rejected and failure action is dispatched with an error as the payload.

For now there are three effect creators you can use:

  • takeEveryPromiseAction
  • takeLeadingPromiseAction
  • takeLatestPromiseAction

If you would like to have more control over your saga you can manually resolve and dispatch individual actions.

//Promise actions
function* signUp(action) {
    try {
        const response = yield axios.request(...);

        yield put(actions.signUp.success(response));
        resolvePromiseAction(action, response);
    } catch(err) {
        yield put(actions.signUp.failed(err));
        rejectPromiseAction(action, err);
    }
}

export authSaga = [
    takeEvery(actions.signUp.request, signUp)
];

Depending on the result the returned promise from dispatch(...) will either resolve or reject.

dispatch helper

Dispatch helper is used to dispatch the action and if it is a promise action it will wait until the action is resolved. It uses put and putResolve under the hood and returns appropriate effect depending whether the action is a promise action.

//Action creators
import {createPromiseAction} from 'redux-saga-promise-actions';
import {createAction} from 'typesafe-actions';

const getProfile = createPromiseAction(
    'GET_PROFILE_REQUEST', 
    'GET_PROFILE_SUCCESS', 
    'GET_PROFILE_FAILURE'
)<
    undefined,
    {email: string; name: string},
    undefined
>();

export const completeLoading = createAction('COMPLETE_LOADING')();
//Dispatch helper
import {dispatch} from 'redux-saga-promise-actions';

function* signUp() {
    // getProfile is a promise action, it will wait until it gets resolved
    yield dispatch(actions.getProfile.request());

    // completeLoading is not a promise action, the action will be dispatched and it won't block the saga
    yield dispatch(actions.completeLoading());
}

redux-saga-promise-actions's People

Contributors

chmac avatar tomekkleszcz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

redux-saga-promise-actions's Issues

redux-saga-promise-actions show error

when i use the code "export authSaga = [ takeEvery(actions.signUp.request, signUp)];" in my reactjs program. my program will show "
"Parsing error: Unexpected token, expected "{" (65:7)" error. How to solve the problem? Thank you very much.

Feature Request: Add a generic try / catch saga wrapper

Again, like #1, I'm looking at redux-saga-promise, and their implementPromiseAction() saga wrapper would be a great addition. Here's the code:

https://github.com/adobe/redux-saga-promise/blob/fd5c964e295ebc12cb1d342037f0a87490217700/src/index.js#L56-L66

I can use it like so:

function * handleMyAction (action) {
  yield call(implementPromiseAction, action, function * () {
    const { path } = action.payload
    return yield call(fsPromises.readFile, path, { encoding: 'utf8' })
  })
}

It essentially automatically wraps the saga in a try / catch, and dispatches the resolve / reject method of the action appropriately. It's just one call instead of one to resolvePromiseAction() and one to rejectPromiseAction().

@tomekkleszcz Saw you're already working on #2, that's AWESOME! I'd be happy to lend a hand if it's helpful in any way.

Having trouble typing resolvePromiseAction()

Firstly, thanks again fro all the help with my last round of issues. Really appreciate it. Also, happy new year!

I've updated to the latest package, and updated typescript to support template literal types. I'm seeing an issue trying to call resolvePromiseAction(). The invocation is:

resolvePromiseAction(action, response)

Then the typing error is:

Argument of type '[R]' is not assignable to parameter of type 'R extends undefined ? [] : [R]'.ts(2345)

You can see the code in full context here if that's helpful.

I think my type is correct, but somehow the ... spread operator doesn't seem to work. I guess I'm missing something. What would be the correct way to invoke resolvePromiseAction in TypeScript? Open to a PR which adds that to the docs?

Error running build

Love this project, anyone running into this typescript issues when building.

Failed to compile.

MenuSafetyLLCVer1/node_modules/redux-saga-promise-actions/dist/index.d.ts(27,126):
'>' expected. TS1005

25 |  * @param {Z} Z Failure action payload
26 |  */

27 | export declare function createPromiseAction<Type extends TypeConstant, X, Y, Z>(type: Type): <X, Y, Z>() => PromiseActionSet<${Type}_REQUEST, ${Type}_SUCCESS, ${Type}_FAILURE, X, Y, Z>;
| ^
28 | /**
29 | * Create an object containing three action-creators.
30 | * @param {string} requestType Request action type

error Command failed with exit code

Feature Request: Add a dispatch effect

Firstly, thanks for this package, it looks awesome, and especially that its in TypeScript.

At first I found the package from Adobe which is very similar, but written in JavaScript. :-( It has 1 feature which I don't see in your package, and which I guess is simple to add. It exposes a new effect called dispatch. This effect returns the put or putResolve effect based on whether the dispatched action is a promise action or not. It's a nice wrapper that makes it easier to always use dispatch and know that promise actions will be awaited automatically.

The code for the effect is quite simple, here's a direct link:

https://github.com/adobe/redux-saga-promise/blob/fd5c964e295ebc12cb1d342037f0a87490217700/src/index.js#L78-L94

Example app

Create an example app in the example directory in order to present the correct usage of this library.

Interop with typed-redux-saga?

Firstly, you've already answered a TON of my questions and added AWESOME new features, so feel free to silently ignore or close this topic if you've reached your limit!

I'm trying to get putResolve() from typed-redux-saga to play nicely with this package. The typted-redux-saga package introduces some extra types so that I can do:

import {putResolve} from 'typed-redux-saga'

const actionCreators = createPromiseAction(
  "foo/request",
  "foo/success",
  "foo/failure"
)<undefined, { token: "string" }, { error: Error }>();

function* saga() {
  const val = yield* putResolve(actionCreators.request());
}

In the scenario above, TypeScript can figure out the type of val. However, when I try it with a PromiseAction, the resulting type of val is PromiseAction<string, PayloadType, ResolveType>.

I can't figure out how to tell TypeScript that the result is the ResolveType. My TypeScript fu is way over stretched here. The generics and so on are just not something I know well enough.

I'm wondering if I can request that the typed-redux-saga package adds support for PromiseActions so that the types it ships understand how a PromiseAction is treated. I was trying to figure out how to do that to submit a PR or open a discussion there, but thus far I'm lost. If you have any pointers that would push me in the right direction, I'll be most grateful. ๐Ÿ‘

EDIT: VSCode shows the following type for val:

const val: PromiseAction<"foo/request", undefined, {
    token: "string";
}>

So val.meta.promiseAction is of type boolean.

Proper types for `dispatch`

This is an awesome library! thanks for building it!

I found it while I was implementing my own version of it, and your implementation is exactly what I wanted.

I have a proposal to introduce a change to the dispatch typing that detects the PromiseAction and types the return properly:

// x is PromiseAction<> even if its not true in runtime
const x = yield* dispatch(test.request())

Something like this does the trick:

export function dispatch<T extends string, R, E>(
  action: PromiseAction<T, E, R>
): SagaGenerator<R, PutEffect<PromiseAction<T, E, R>>>

// x is now the proper return type
const x = yield* dispatch(test.request())

it could preserve the normal typings when the action isnt a PromiseAction by simply making it an overload and falling back to the normal type:

export function dispatch<T extends string, R, E>(
  action: PromiseAction<T, E, R>
): SagaGenerator<R, PutEffect<PromiseAction<T, E, R>>>

export function dispatch<A extends Action>(action: A): SagaGenerator<unknown, PutEffect<A>>

let me know what you think

Dispatch type returns TResolveType rather than Promise<TResolveType>

@tomekkleszcz More typing adventures!

I forked a redux-saga & typescript codesandbox to illustrate what I think I've found...

https://codesandbox.io/s/black-glitter-88usy?file=/src/app/store.ts

Here's the pertinent code sample:

const a = createPromiseAction('a', 'b', 'c')<
  undefined,
  { result: string },
  undefined
>()
const result = store.dispatch(a.request())

With the promiseMiddleware added (line 23) the type of result is {result: string}, but when I remove that middleware (line 24) then the type becomes Promise<{result: string}>.

Could it be that adding the promiseMiddleware somehow tells TypeScript that the return value of .dispatch() is the resolved value, rather than a promise?

Add a prepare/transformer option

When calling actions, we might want to do something first before putting it in an action.

A good real time example is passing a Date object. We might want to serialize it to a string first, but that is seemingly currently not possible in redux-saga-promise-actions.

Typescript as a devDependency?

Tried to install this from git, but I was missing the dist/ folder. Then I cloned, ran yarn, and got a "tsc not found" error. What about adding a specific TypeScript version as a devDependency?

Also, what about adding a lockfile? Maybe you're using npm? I usually pick my package manager based on the lockfile, or default to yarn if there isn't one. :-)

Typed putResolve snippet that might be helpful

Again, really, thanks a lot for all your help with this package, I'll buy you a beer if you're ever in Berlin!

I finally figured out how to get putResolve() from typed-redux-saga to play nicely (no reply yet on my question).

Here's my code:

export type ExtractPromiseResolveType<
  T = PromiseAction<any, any, any>
> = T extends PromiseAction<any, any, infer R> ? R : never;

export const getAsyncPromiseResolveValue = <
  T extends PromiseAction<any, any, any>
>(
  resolved: T
) => resolved as ExtractPromiseResolveType<T>;

Then I use it like this:

import { putResolve } from "typed-redux-saga/macro";

...
const result = getAsyncPromiseResolveValue(yield* putResolve(requestAction()));

It's quite a hack, and requires an extra function call at runtime, but it means that, for me anyway, result is not accurately typed. Happy to submit a PR adding this as a suggestion to the docs, or could post a link to this issue somewhere if you think it's useful to others, just let me know. ๐Ÿ‘

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.