GithubHelp home page GithubHelp logo

omichelsen / redux-promise-middleware-actions Goto Github PK

View Code? Open in Web Editor NEW
32.0 2.0 3.0 1014 KB

Redux action creator for making async actions compatible with redux-promise-middleware

License: MIT License

TypeScript 98.80% JavaScript 1.20%

redux-promise-middleware-actions's Introduction

redux-promise-middleware-actions

Build Status Coverage Status npm bundle size (minified + gzip)

Create Redux actions with a type and payload in a standardized way. Inspired by redux-actions but simpler and with special support for asynchronous actions (promises).

Has no dependencies and is tiny (~680 bytes gzipped). First class TypeScript support.

Works with redux-promise-middleware to handle asynchronous actions by dispatching pending, fulfilled and rejected events based on the state of the input promise.

Goals of this library:

  • Reference action creators directly - no need to maintain an action type enum/list
  • Automatically generate actions for pending, fulfilled and rejected outcomes of a promise payload
  • Have statically typed access to all action types - no need to manually add a type suffix like "_PENDING"
  • TypeScript support so asynchronous actions can't be confused for normal synchronous actions

Note: If you are using TypeScript this library requires TypeScript 3. For TypeScript 2 use version 1 of this library.

Installation

You need to install this library as well as redux-promise-middleware.

npm install redux-promise-middleware-actions redux-promise-middleware

Include redux-promise-middleware when you create your store:

import promiseMiddleware from 'redux-promise-middleware';

composeStoreWithMiddleware = applyMiddleware(
  promiseMiddleware,
)(createStore);

NOTE: This library is not yet compatible with the promiseTypeSuffixes option of redux-promise-middleware

Usage

Synchronous action

Synchronous actions works exactly like redux-actions. You supply a function that returns whatever payload the action should have (if any).

import { createAction } from 'redux-promise-middleware-actions';

export const foo = createAction('FOO', (num) => num);

dispatch(foo(5)); // { type: 'FOO', payload: 5 }

When handling the action in a reducer, you simply cast the action function to a string to return the type. This ensures type safety (no spelling errors) and you can use code navigation to find all uses of an action.

const fooType = foo.toString(); // 'FOO'

Asynchronous action

When you create an asynchronous action you need to return a promise payload. If your action is called FOO the following events will be dispatched:

  1. FOO_PENDING is dispatched immediately
  2. FOO_FULFILLED is dispatched when the promise is resolved
    • ... or FOO_REJECTED is dispatched instead if the promise is rejected
import { createAsyncAction } from 'redux-promise-middleware-actions';

export const fetchData = createAsyncAction('FETCH_DATA', async () => {
  const res = await fetch(...);
  return res.json();
});

dispatch(fetchData()); // { type: 'FETCH_DATA_PENDING' }

An async action function has three properties to access the possible outcome actions: pending, fulfilled and rejected. You can dispatch them directly (in tests etc.):

dispatch(fetchData.pending());          // { type: 'FETCH_DATA_PENDING' }
dispacth(fetchData.fulfilled(payload)); // { type: 'FETCH_DATA_FULFILLED', payload: ... }
dispacth(fetchData.rejected(err));      // { type: 'FETCH_DATA_REJECTED', payload: err, error: true }

But normally you only need them when you are writing reducers:

case fetchData.pending.toString():   // 'FETCH_DATA_PENDING'
case fetchData.fulfilled.toString(): // 'FETCH_DATA_FULFILLED'
case fetchData.rejected.toString():  // 'FETCH_DATA_REJECTED'

Note that if you try and use the base function in a reducer, an error will be thrown to ensure you are not listening for an action that will never happen:

case fetchData.toString(): // throws an error

Reducer

To create a type safe reducer, createReducer takes a list of handlers that accept one or more actions and returns the new state. You can use it with both synchronous and asynchronous action creators.

createReducer(defaultState, handlerMapsCreator)

import { createAsyncAction, createReducer } from 'redux-promise-middleware-actions';

const fetchData = createAsyncAction('GET', () => fetch(...));

const defaultState = {};

const reducer = createReducer(defaultState, (handleAction) => [
  handleAction(fetchData.pending, (state) => ({ ...state, pending: true })),
  handleAction(fetchData.fulfilled, (state, { payload }) => ({ ...state, pending: false, data: payload })),
  handleAction(fetchData.rejected, (state, { payload }) => ({ ...state, pending: false, error: payload })),
]);

reducer(undefined, fetchData()); // { pending: true, data: ..., error: ... }

asyncReducer(asyncActionCreator)

It can get tedious writing the same reducer for every single async action so we've included a simple reducer that does the same as the example above:

import { asyncReducer } from 'redux-promise-middleware-actions';

const fetchData = createAsyncAction('GET', () => fetch(...));
const fetchReducer = asyncReducer(fetchData);

fetchReducer() // { data?: Payload, error?: Error, pending?: boolean }

You can also combine it with an existing reducer:

export default (state, action) => {
  const newState = fetchReducer(state, action);

  switch (action.type) {
    case 'SOME_OTHER_ACTION':
      return { ... };
    default:
      return newState;
  }
};

Metadata

You can add metadata to any action by supplying an additional metadata creator function. The metadata creator will receive the same arguments as the payload creator:

createAction(type, payloadCreator, metadataCreator)

const foo = createAction(
  'FOO',
  (num) => num,
  (num) => num + num
);

dispatch(foo(5)); // { type: 'FOO', meta: 10, payload: 5 }

createAsyncAction(type, payloadCreator, metadataCreator)

const fetchData = createAsyncAction(
  'FETCH_DATA',
  (n: number) => fetch(...),
  (n: number) => ({ n })
);

dispatch(fetchData(42));
// { type: 'FETCH_DATA_PENDING', meta: { n: 42 } }
// { type: 'FETCH_DATA_FULFILLED', meta: { n: 42 }, payload: Promise<...> }
// { type: 'FETCH_DATA_REJECTED', meta: { n: 42 }, payload: Error(...) }

Custome Type Delimiters

You can specify a different type delimiter for your async actions:

createAsyncAction(type, payloadCreator, metadataCreator, options)

const foo = createAsyncAction(
  'FETCH_DATA',
  (n: number) => fetch(num),
  undefined,
  { promiseTypeDelimiter: '/' }
);

dispatch(foo());
// { type: 'FETCH_DATA/PENDING' }
// { type: 'FETCH_DATA/FULFILLED', payload: ... }
// { type: 'FETCH_DATA/REJECTED', payload: ... }

Acknowledgements

Thanks to Deox for a lot of inspiration for the TypeScript types.

redux-promise-middleware-actions's People

Contributors

cjroebuck avatar greenkeeper[bot] avatar omichelsen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

redux-promise-middleware-actions's Issues

createReducer allows setting unknown fields to new state

image

See line 17 nane: action.payload.name, TS is nit complaining about that I typed nane instead of name.

If I add the type manually:

image

Then I see the error fine.

Is there a way to have the error without the manual type?

Thanks

An in-range update of @types/node is breaking the build 🚨

The devDependency @types/node was updated from 10.12.11 to 10.12.12.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

@types/node is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Typescript: Is there are a way to type actions?

const myAction = createAction(
  'ACTION_TYPE',
  (payload: any) => payload,
);

interface IMyAction {
  type: ??? // is there a way to infer type here from myAction or
  // currently only way is extract 'ACTION_TYPE' to const (const  ACTION_TYPE = 'ACTION_TYPE')
  payload: any;
}

const myReducer = (
  state = initialState,
  action: IMyAction,
) => {
  switch (action.type) {
    case String(myActionCreator):
      return action.payload;
    default:
      return state;
  }
};

Typescript dispatch return type

Hi, Is there an example on how to use the library with Typescript, How do I transform the return of the createAsyncAction after dispatching it, for example, I have an createGroup action and after dispatching it, the return type is this:

var createGroup: (data: GroupForm) => {
    type: "CREATE_GROUP";
    payload: Promise<NormalizedGroup>;
    meta: number;
}

where I'm expecting this:

var createGroup: (data: GroupForm) => Promise<{
    value: NormalizedGroup;
    action: Action<SomeAction>;
}>

createAsyncAction produces Uncaught (in promise) error

Hi,
I've created an async action , (see the function below), and when I call it in my login component , and get an error (i.e 400) the console pops this error Uncaught (in promise)

export const signInWithEmailAndPassword = createAsyncAction(
  'SIGN_IN',
  async ({ email, password }) => {
    try {
      const response = await axios.post('signin', {
          email,
          password,
        })
      );
      return response.data.user;
    } catch (error) {
      return Promise.reject(error);
    }
  }
);

My login component looks like this


import React, { useEffect } from 'react';
import { Link, Redirect } from 'react-router-dom';
import Layout from 'components/Layout';
import { isAuth } from 'utils/helpers';
import {
  signInWithEmailAndPassword
} from 'services/Auth';
import { connect} from 'react-redux';
import SignInForm from 'components/SignIn/RenderSignInForm';

const Signin = ({signInWithEmailAndPassword}) => {

  const onSubmit = (values) => signInWithEmailAndPassword(values);
  return (
        <SignInForm onSubmit={onSubmit} />
  );
};

export default connect(null, {signInWithEmailAndPassword})(Signin);

(SignInForm just returns a simple form component)

I found out that if I add a catch to onSubmit the error disspapears, why though? I thought the whole point of redux-promise-middleware-actions is to handle the success and error in the action creator + redux store

Please help anyone?

An in-range update of @types/node is breaking the build 🚨

The devDependency @types/node was updated from 11.11.3 to 11.11.4.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

@types/node is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of @types/node is breaking the build 🚨

The devDependency @types/node was updated from 12.12.5 to 12.12.6.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

@types/node is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

createReducer is missing metadata

First of all, congrats for the new createReducer. This is super helpful as we don't have to define types anymore!

The only issue I'm seeing is that I can't seem to grab the metadata, only the payload in fulfilled state

Q: Is it possible to combine an array of asyncReducer?

Hello, I am having type problems when doing something like this:

const emit = (args) => new Promise(...); /* some function -- Returns Promise<DataFormat> */

const setAll = createAsyncAction('setAll', (state: DataFormat) => emit('setAll', state));
const sync = createAsyncAction('sync', () => emit('sync'));

const allActions =  [
  setAll,
  sync,
];


const reducers = allActions.map(action => asyncReducer<string, DataFormat, unknown>(action));

// @ts-ignore -- complains of reducer returning something incompatible no matter what I tried.
const reducer: typeof reducers[0] = (state, action) => {
  // Preferably I could do reducers.reduce((s, r) => r(s, action), state))

  let newState = { ...state };
  reducers.forEach(reducer => {
    newState = reducer(newState, action);
  });

  return newState;
}

export default reducer;

could maybe somebody help me out here? I would like to use 6-7 async action on the same store object (set, get, update, delete, etc...)

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.