Code Monkey home page Code Monkey logo

redux-promise-middleware's People

Contributors

adam187 avatar aecorredor avatar aguismo avatar anajavi avatar avocadowastaken avatar comerc avatar dependabot[bot] avatar extg avatar franklixuefei avatar ianks avatar joshkel avatar kbsanders avatar kuuup avatar mik01aj avatar mikew avatar pburtchaell avatar piu130 avatar pjvds avatar rahulbdominic avatar ramast avatar ramiloif avatar randycoulman avatar roboslone avatar rotemmiz avatar samuraime avatar selrond avatar spicypete avatar therewillbecode avatar toddmazierski avatar tomatau 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  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  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  avatar  avatar  avatar  avatar

Watchers

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

redux-promise-middleware's Issues

Original promise can be returned to avoid anti-pattern

Version

3.0.0

Test Case

    it('propagates the original promise', async () => {
      const actionDispatched = store.dispatch({
        type: defaultPromiseAction.type,
        payload: Bluebird.resolve(promiseValue)
      });

      // Expect that the promise returned has bluebird functions available
      expect(actionDispatched.any).to.be.a('function');

      await actionDispatched.then(({ value, action }) => {
        expect(value).to.eql(promiseValue);
        expect(action).to.eql(fulfilledAction);
      });
    });

Expected Behavior

The original promise should be chained and returned.

Actual Behavior

A new native promise is created.


I can't see any reason for creating a new promise and rejecting/resolving it. This perpetuates the deferred anti-pattern. When changing the code to return the original promise, all of the test suite continues to pass.

If there is a reason that a new promise must be created (and that's fine if there is), then the reason is not covered in the tests.

The code could be changed to:

      return promise.then(
        (value = null) => {
          const resolvedAction = getAction(value, false);
          dispatch(resolvedAction);
          return { value, action: resolvedAction };
        }).catch((reason = null) => {
          const rejectedAction = getAction(reason, true);
          dispatch(rejectedAction);
          const rejectedDetails = { reason, action: rejectedAction };
          throw rejectedDetails;
        });

to allow any promise library to continue working as expected.

Happy to submit a PR if this is a valid change.

Integrate with redux-actions

i was trying to figure out how integrate redux-promise-middleware with redux-actions,
is this posible or it is not necessary to use both components?

Dispatch additional actions?

So I have been using the middleware in a project that has a spinner that shows whenever a network request is made (and hides when the request is fulfilled). This project also shows an error notification whenever a network request is rejected.

Instead of handling this on a request-by-request basis, I created a second middleware that looks at the type of the action and checks via regex if it is appended with one of the promise types. If it is, then an action is dispatched to tell me that a request is pending or rejected.

For example, if you were to log the types (beware—this is very pseudo), it would look like this:

console.log(action.type) => 
GET_POSTS_PENDING
PENDING
GET_POSTS_FULFILLED
FULFILLED

Or, if rejected:

console.log(action.type) => 
GET_POSTS_PENDING
PENDING
GET_POSTS_REJECTED
REJECTED

I then have a reducer that handles the PENDING, FULFILLED and REJECTED actions. My spinner and error notification components are connected to this reducer.

My thought is this: why have a second middleware handle dispatching these actions? It could be moved into redux-promise-middleware and enabled via a configuration option. The types dispatched would just be the promise types used already, so that could be configured as well. For example:

composeStoreWithMiddleware = applyMiddleware(
  promiseMiddleware({
   requests: true // dispatch additional request actions
  })
)(createStore);

Typically, I would prefer to keep the API of the project minimal, but I think this would be a benefit that many people could use. Spinners and errors are both pretty common UI elements lots of projects use. I'm wondering what the thoughts on this are. cc/ @tomatau

should meta attribute be preserved ?

Example use case:

Performing a deletion (eg. DELETE /api/thing/23).
The backend responds with a simple true/false indicating outcome of deletion.

To update the store, I require the id (23) but it's not available through the FULFILLED action. I believe setting a 'meta' attribute via the action creator should allow me to pick it up later, but it is being discarded in the subsequently dispatched actions.

Error when resolved promise returns null

This line expects existing resolved, and trying to read its meta.
https://github.com/pburtchaell/redux-promise-middleware/blob/master/src/index.js#L34

Which makes this error:

Unhandled rejection TypeError: Cannot read property 'meta' of undefined
    at eval (eval at <anonymous> (http://localhost:8080/build/app.js:2779:2), <anonymous>:52:24)
    at eval (eval at <anonymous> (http://localhost:8080/build/app.js:1591:2), <anonymous>:21:50)
    at dispatch (eval at <anonymous> (http://localhost:8080/build/app.js:2797:2), <anonymous>:46:18)

My code

export function login() {
  return ({firebase}) => ({
    type: 'LOGIN',
    payload: {
      promise: firebase.authWithOAuth('facebook', getOptions('facebook'))
        .then(auth => {
          const user = createUserFromFacebook(auth);
          return firebase.set(['users', user.id], user.toJS());
        })
    }
  });
}

Btw, I don't understand why resolved is supposed to by always defined.

Completely new dispatch for _FULFILLED and _REJECTED

Currently we call next() inside the middleware for either rejected or fulfilled actions.

Say you want the _FULFILLED action to return a thunk for whatever reason, now we have some issues where the order or configured middleware limits this possibility. Alternatively you may need thunk actions to later dispatch promises! Right now we can't get both of these middleware to communicate without adding one of these middleware to our store configure twice.

An easy solution to this is to fire a completely new store.dispatch per _FULFILLED or _REJECTED instead of calling next.. this way other middleware inserted before promiseMiddleware can be applied to the _FULFILLED or _REJECTED action types.

Reducing boilerplate in reducers

This is really the same question as #35, but that issue seems to have taken a different track.

It seems like pretty much everyone that is using redux-promise-middleware (or any other async middleware) is keeping the promise state in the redux store.

Stealing @pburtchaell example from #35, a typical reducer might look like.

const defaultState = {
  isPending: undefined,
  isFulfilled: undefined,
  isRejected: undefined
}

switch (action.type) {
    case `${type}_PENDING`:
      return {
        ...state,
        ...defaultState,
        isPending: true
      };

    case `${type}_FULFILLED`:
      return {
        ...state,
        ...defaultState,
        isFulfilled: true,
        isSignedIn: true,
        token: action.payload
      };

    case `${type}_REJECTED`:
      return {
        ...state,
        ...defaultState,
        isRejected: true,
        error: action.payload
      };

   default: return state;
}

In the above example we are manually keeping isPending, isFulfilled and isRejected updated in state.

Proposal

What I would like to propose is that instead of removing the payload.promise from the dispatched action, we replace it with an object that represents the promise state.

So for example if you dispatched an action that looked like this...

{
  type: 'ADD_TODO',
  payload: {
    promise: somePromise,
    data: { id: 'some_id' }
  }
}

the actions that would get sent to the reducer would look something like...

// PENDING action
{
  type: 'ADD_TODO_PENDING',
  payload: {
    promise: {
      isPending: true,
      isResolved: false,
      isRejected: false
    },
    data: { id: 'some_id' }
  }
}

// SUCCESS action
{
  type: 'ADD_TODO_SUCCESS',
  payload: {
    promise: {
      isPending: false,
      isResolved: true,
      isRejected: false,
      value: 'FOO' // <-- value from resolved promise
    },
    data: { id: 'some_id' }
  }
}

// ERROR action
{
  type: 'ADD_TODO_ERROR',
  payload: {
    promise: {
      isPending: false,
      isResolved: false,
      isRejected: true,
      value: 'ERROR' // <-- error from rejected promise
    },
    data: { id: 'some_id' }
  }
}

I'm not completely sold on the above action object shape or property names yet. But I wanted to put this out there to get feedback and see what others think about such an idea.

Thanks 😄

Issue with react-native

This notation causes an error on react-native 0.14.2:

    next({
        type: `${type}_${PENDING}`,
        ...!!data && { payload: data },
        ...!!meta && { meta }
      });
Error: In this environment the target of assign MUST be an object.This error is a performance optimization and not spec compliant.

rewriting with Object.assign solves issue:

    next(Object.assign(
        {
          type: `${type}_${PENDING}`
        },
        ...!!data && { payload: data },
        ...!!meta && { meta }
      ));

Allow optimistic updates

Currently the payload is not returned in the PENDING action. This prevents optimistic updates.

The payload is currently the promise. Instead the promise could be nested along side the initial data.

{
    payload: {
        data: formData,
        promise: WebUtils.submitForm(formData)
    },
    types: ...
}

That way the action still adheres to FSA but allows for optimistic updates.

The dispatched actions don't need an undefined payload/meta

Right now, redux-promise-middleware dispatches actions with the keys meta and payload with an undefined value, even if they were not passed (for instance, the pending action, without any data provided will have an undefined payload).

When testing actions, it forces me to specify the undefined values if I want to use a deepEqual. I agree that this is not big problem, but since the FSA spec states that this keys are not necessary, I don't think that redux-promise-middleware should add them.

If you agree, I'll gladly submit a PR.

Release notes don't mention required change when calling applyMiddleware

In 2.0.0 the middleware must now be initiliazed like this:

composeStoreWithMiddleware = applyMiddleware(
  promiseMiddleware()
)(createStore);

Previously it was:

composeStoreWithMiddleware = applyMiddleware(
  promiseMiddleware
)(createStore);

The () isn't mentioned in the release notes and it took me a while to figure out what the problem was.

Support for bluebird

Hi,

Bluebird expects all rejects to be of class Error. This lib should include something like. I don't think it should be enforced but at least either have an example or build this class in. Thoughts?

// See: http://stackoverflow.com/questions/31089801/extending-error-in-javascript-with-es6-syntax
// We need this for stack capture
class ExtendableError extends Error {
    constructor(message) {
        super(message);
        this.name = this.constructor.name;
        this.message = message;
        Error.captureStackTrace(this, this.constructor.name)
    }
}
// Use this with a promises when you need extra data:
// https://github.com/petkaantonov/bluebird/blob/master/docs/docs/warning-explanations.md#warning-a-promise-was-rejected-with-a-non-error
class PromiseError extends ExtendableError {
    constructor(message, {data}) {
        super(message);
        if (data) {
            this.data = data;
        }
    }
}

Best way to handle specific errors globally

So we have a use case in our application where we would like to always dispatch a logout action when ever we get a 401 back from our API. Likewise I would like to have a global error action dispatched for every 500> response that we get back.

Is there a good way to do this with redux-promise-middleware?

I was thinking that we might be able to pass in configuration to the initializer where we could provide handlers for these specific cases.

How to differentiate between the same action dispatched multiple times consecutively?

I've got an action creator deleteItem() that takes an array of ids. Inside the action I iterate through the array and dispatch an action with the type DELETE_ITEM and a promise making an api request (to /api/item/${id}).

Now, for the PENDING phase of this action-promise everything is perfect because I have also attached data: id to the dispatched action, so I can get that id from action.payload in the function that takes care of the pending phase. But for FULFILLED I no longer have that id and so I can't determine which id I need to remove from the state.

Initially I tried storing that id in the state during the pending phase but I quickly realized this isn't helpful since the requests are async.

Ideally, having that id as a prop (like data) in the FULFILLED phase would solve my problem, but I feel like there must be a reason it isn't included. I know the docs say it's for optimistic updates only, but why?

How should I be handling this scenario?

Change reject reason to instance of Error

Because https://github.com/petkaantonov/bluebird/blob/master/docs/docs/warning-explanations.md#warning-a-promise-was-rejected-with-a-non-error

Of course, it's ok from JavaScript point of view. I had to disable this warning in https://github.com/este/este, because I can't ensure all third party libraries will follow this rule. Still, it's a good advice.
Quick fix:

var error = new Error('fok');
error.reason = reason;
error.action = rejectedAction;
reject(error);

If you want to improve DX, use https://github.com/julien-f/js-make-error

Question - setup react-native with promise

Hi ,

I am trying to setup my react-native to use the redux promise middleware and using Fetch.
Here is the code below for my actions file:

return {
  type: GetData,
  payload: {
    promise: fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data)
    })
    .then( function(response) {
      return response.json(); 
    })
    .then( function (json) {
      return json;
    })
  }
}

And in my reducer file I do the following:

switch (action.type) {
    case GetData:
    return { ...state, data: payload }
    default:
    return state;
  }

I placed a debugger in my switch case reducer but I am not getting a response back.
However, if I instead have the following in my actions file (without the promise key):

return {
  type: GetData,
  payload: fetch(url, { ...same as above... })
}

I get a promise back in my reducer, but it does not contain any data.

Can anyone give me an example of using the redux promise along with react-native fetch?
Thank you in advance!

Tests

We need to add tests to ensure changes are 💯 and do not break the middleware.

_network2.default.get is not a function

I was following the complex example.... this is my actions file

`import * as types from '../../constants/actionsConstants';
import network from '../../utils/network';

export const getAll = () => ({
type: types.TRAINEES_FETCH,
payload: network.get({
resource: 'player'
})
});`

when I cakk getAll I get an error
_network2.default.get is not a function

I am using react native 0.26.2
react: 15.0.2
react-native: 0.26.2
redux 3.5.2
react-redux: 4.4.5
redux-promise-middleware: 3.0.0,

Cancelable promises

Cancelable Promises have advanced to stage 1 at TC39. I'm creating this issue as a reminder that the middleware will need to be upgraded in the future to support this (if it becomes a part of the spec).

I imagine the middleware will simply dispatch a third action type, e.g., FOO_ACTION_CANCELLED.

Reminder: stage 1 is a feature "worth working on" so cancelable promises are speculative.

How to dispatch supplementary actions?

Prior to using redux-promise-middleware, I was using redux-thunk middleware for my asynchronous actions. The promise based approach is a lot nicer. However, I have lost the flexibility to dispatch actions at arbitrary points in action processing, and can't see how to mitigate that.

Specifically, I am dispatching a fetch to authenticate the user, and on success, I want to change the router path, using updatePath from redux-simple-router. Here's my action creator code:

export function requestLogin(email, password) {
  return {
    type: REQUEST_LOGIN,
    payload: {
      promise: new Promise((resolve, reject) => {
        fetch(`${baseUrl}/api/v0/authenticate/password`, {
          method: 'POST',
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            email: email,
            password: password
          })})
          .then(response => response.json())
          .then(json => {
            // want to updatePath for the route here:
            //dispatch(updatePath('/'));
            resolve(json);
          })
          .catch(error => {
            reject(error);
          });
      })
    }
  };
}

Any suggestions on how to handle this?

Question: Is passing additional data to the reducer via the `meta` property the intended way to do so?

I was looking for a way to pass additional data in my actions to my reducer so that the it can decide if that action actually needs to be dispatched. For example, if a user attempts to load the same resource twice, I can turn that into a no-op in the reducer by comparing the previous id with the pending one.

// in a react component
actions.loadFilter(id);

// filterActions.js
export function loadFilter(id) {
    return {
        type: types.LOAD_FILTER,
        id: id, // <--- doesn't get passed in the LOAD_FILTER_PENDING action
        payload: {
            promise: filtersAPI.getFilter(id),
            id: id // <---  this neither
        },
        meta: {
           id: id // <--- this does work, however is this the intended behavior of the library?
        }
    };
}
//when the above is dispatched, this is the resulting action object:
// {type: "LOAD_FILTER_PENDING", meta: { id: 123 }}

// filtersReducer.js
        case types.LOAD_FILTER + '_PENDING':
            // if they request the same filter consecutively then it's a no-op
            let prevId = _.get(state, 'currentFilter.data.id');
            if(prevId && prevId === action.meta.id) {
                return state;
            }
            // ...other stuff removed for brevity...

This behavior doesn't seem to be documented so I thought I would ask if this is how the library was intended to be used and if doing this could cause breakage in the future.

I'm new to redux and react, so sorry if I'm stupid about all this 😄

async/await syntax

Hello,
I want to use ES7's async/await syntax with the middleware, but oddly I can't make it works.

Here is the syntax I use :

export default function fetchPrelevements(jour) {

    return {

        type: "FETCH_PRELEVEMENTS",

        payload: {

            promise: async function () {

                const { data } = await axios.get("/api/prelevements", {
                    params{ debut: jour, fin: jour }
                })

                return data

            }

            // This syntax works :
            //promise: axios.get("/api/prelevements", {
            //    params: { debut: jour, fin: jour }
            //}).then(({ data }) => data)

        }

    }

}

The action is dispatched in the two cases, but with the async/await syntax it dispatches the function without being treated by the middleware, so without any suffixes.

Any idea ? :)

Examples

It would be good to add examples; sometimes seeing actual code is better than reading the README.

  • Simple example without React
  • A README with some concise explanations of action creators you could use
  • Example should demonstrate:
    • use the meta prop for more than just suffix configuration
    • Combining redux-promise-middleware with redux-thunk
    • How to change routes with React Router when a promise is fulfilled

EDIT: With the release of version 3.0.0, there is a simple and complex example of how to use the middleware. The simple example works, but the complex example is not finished. Help is wanted!

middleware won't work with react-native

Getting the following error:

Error: In this environment the target of assign MUST be an object.This error is a performance optimization and not spec compliant.
 stack: 
  Object.assign 

The problem is in this line. React-native uses a polyfill that won't allow something like _.extend({}, false) which is the result of an action without meta or data.

As a workaround, I can specify empty meta and data in every action but this makes the code too verbose.

Something like ACTION_DONE?

Sometimes we need to change a state when the promise is done/completed (either fulfilled or rejected), such as re-enable the button. It would be helpful and keep DRY if this library could provide such action :-)

Writing test cases for async actions written using redux promise middleware

Hi,

Can u please let me know how to write test cases for async actions created using redux promise middlware which is making an http request.

I am trying to use redux mock store and sinon.js for mocking but facing issues,
TypeError: dispatch(...).then is not a function when i try to call a action creator

return dispatch => {
// show progress bar
return dispatch({
type: projectActionsType,
payload: post(wsConst.baseURL + wsConst.save, {
un:'abc',
pw:123
})
}).then(() => {
//hide progress bar
});
};

How can i write test case for same with response Fulfilled, rejected and pending

Please let know how can i mock my requests and write testcases for same

Thanks for any help

sequential async actions?

Hello,
First of all thanks for your work on this middleware.
I'm having some trouble following an example in the docs. I'm still somewhat of a newcomer to react/redux so please bear with me. Currently I have an action creator that returns a promise from a connect() function (essentially it's a login):

export function connect(connectionParams) {
  return {
    type: CONNECT,
    payload: {
      promise: authUtil.connect(connectionParams),
    }
  };
}

This is working fine. But now I want to dispatch another async action when the connect() promise resolves successfully. In your docs you have 'Dispatching Actions when Promises are Resolved' which seems like exactly what I'm looking for. But in the example provided:

const actionCreator = () => ({
  type: 'FIRST_ACTION_TYPE',
  payload: {
    promise: Promise.resolve({
      type: 'SECOND_ACTION_TYPE'
      payload: ...
    })
   }
});

I'm not clear where in this structure my connect() promise (corresponding to the FIRST_ACTION_TYPE) in this action creator) would go. Also would this still dispatch the _PENDING, and _FULFILLED/_REJECTED actions of the first action type?
Thanks.

Possible Unhandled Promise Rejection (id: 0):

Hi guys,

I am trying to make a request to my restfull api but for some reason I always get [PREFIX]_REJECTED as response and there is only this warning telling me there is a possible unhandled promise rejection.

Warning:
Possible Unhandled Promise Rejection (id: 0):
Network request failed
TypeError: Network request failed
at XMLHttpRequest.xhr.onerror (http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=true&minify=false:30143:8)
at XMLHttpRequest.dispatchEvent (http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=true&minify=false:13221:15)
at XMLHttpRequest.setReadyState (http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=true&minify=false:29599:6)
at XMLHttpRequest._didCompleteResponse (http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=true&minify=false:29481:6)
at http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=true&minify=false:29430:51
at EventEmitter.emit (http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=true&minify=false:12818:23)
at MessageQueue.__callFunction (http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=true&minify=false:11253:23)
at http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=true&minify=false:11157:8
at guard (http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=true&minify=false:11111:1)
at MessageQueue.callFunctionReturnFlushedQueue (http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=true&minify=false:11156:1)

This is my code:

export function login(username,password) {

  return {
    type: actions.LOGIN,
    payload: {
      promise: fetch(environment.serverURL+'/Users/login', {
          method: 'POST',
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            'username': username,
            'password': password
          })
        })
    }
  }
}

authReducer.js

import * as actions from '../actions/actionTypes';

const initialState = {
  username: '',
  isLoggingIn: false,
  isLoggedIn: false,
  token:'',
  userId:0,
  error: null
};

export default function auth(state = initialState, action) {
  switch (action.type) {
    case 'LOGIN_PENDING':
      return Object.assign({}, state, {
            isLoggingIn: true,
            isLoggedIn: false,
            username: action.username
            })
    case 'LOGIN_FULFILLED':
      return Object.assign({}, state, {
  error: null,
  isLoggingIn: false,
  isLoggedIn: true,
  token: action.payload.body.id,
  userId: action.payload.body.userId
})
    case 'LOGIN_REJECTED':
      return Object.assign({}, state,{
  error: action.error,
  isLoggingIn: false,
  isLoggedIn: false,
})
    default:
      return state;
  }
}

Change Meta for Fulfilled action

I'm happy to make a PR for this but wanted to run it past everyone else in an issue first.

My use case: I'm using async actionCreators to make API calls that request updates to persisted data, once fulfilled, I'd like the success action to be distributed over a web-socket using https://github.com/rstuven/redux-via-socket.io. This is achieved by supplying meta options in the action you would like to send to the server, for example meta: { broadcast: true }. I hope to only broadcast the FULFILLED promiseTypeSuffix as other clients don't give a rats fiddle about the pending and error :)

Here's an example of the API:

const addItem = async (body) => {
  const {item} = await itemGateway.addItem(body)
  return {
    payload: { item },
    meta: { broadcast: true }
  }
}

export const addITem = (item) => ({
  type: ADD_ITEM,
  payload: {
    data: { item },
    promise: addItem(item)
  }
})

Here resolving the complete action (without type) to the promise.

I have this already working locally on a project using my own fork... also I'd like to make it work by either resolve a { payload, meta } or just resolving the payload with no change the the meta data (to keep backwards compatibility and remove boilerplate).

Error Actions override payloads

I want to pass my _ERROR action a payload (in addition to the Error object that overrides the payload. I can pass _START and _SUCCESS actions a payload with a data property, but I lose it on the _ERROR action.

Question about a pattern for handling an optimistic update that is rejected

I am using this library in the context of a CRUD application, and so far it does what I need, so thanks for creating it :)

However, I have a question about how to handle an optimistic update that is rejected, and should be rolled back. Please help me if I'm just misunderstanding something, but imagine I have the following scenario:

  1. A user edits some entity in the application, and wishes to save it (using an API)
  2. We dispatch an "update" action that includes a promise and the new entity as the 'data' property
  3. We handle the _PENDING action in the reducer and apply the optimistic update to the state tree (note, at this point I have now lost the previous state for the affected entity)
  4. The promise is then rejected so how can I restore the affected entity back to what it was?

I think there's a simple pattern I just don't see yet, but how are others handling a case like this? Do I need to capture the original entity in my action creator and dispatch some other action in the result of a failure to "roll back" my optimistic update?

Thanks for any guidance!

Consider updating to Redux 1.0 RC

Redux 1.0 RC is out with breaking changes. No further API changes are expected in 1.0 so it may be a good time to port the repo to 1.0 RC (in a branch). We will release 1.0 when the docs are ready.

You can find the comprehensive list of the changes in 1.0 RC against 1.0 alpha in the Redux 1.0 RC release notes. If you haven't ported to alpha yet, you'll need to apply changes from 0.12 to alpha first.

Thank you!
Please make sure to report any issues so we can release a stable 1.0.

Bug when API payload has the same structure as an action

Not sure if this is actually a bug but I just came across this really edge-case-y thing where my API payload had the same structure like an action, which caused me a 3h headache why my promise never reached the fulfilled part, even though the API call was successfull.

My Action looks like this:

export const createCar = (data) => ({
  type: types.CREATE_CAR,
  payload: {
    promise: API.createCar(data)
  }
})

API:

API.createCar = function createCar(data) {
    return fetch(`${API_URL}/cars`, {
      method: 'POST',
      body: JSON.stringify(data)
    }).then((r) => r.json())

Reducer:

  ...

  [`${types.CREATE_CAR}_PENDING`]: (state) => {
    return {
      ...state,
      isPending: true
    }
  },

  [`${types.CREATE_CAR}_FULFILLED`]: (state, { payload }) => {
    return {
      ...state,
      isPending: false,
      data: {
        ...state.data,
        [payload.id]: payload
      }
    }
  },
  ...

Now I was wondering why my action never reached the 'fulfilled' state. This was because my API payload looked like this:

{
  id: 12345
  type: 'car',
  payload: {
    foo: 'bar',
    ...etc.
  }
}

What now happened is that this payload got fired as an action and it never reached the fulfilled state because of that.

I wonder if there is some way around that? currently I'm wrapping the api response in another object so it reaches the fulfilled state but I feel like this should not actually happen. It should always reach one of my pending...fulfilled...rejected states.

Unhandled promise rejection

I have a question about what happens when the payload is a promise that is rejected. I see that the middleware correctly dispatches an event with the _REJECTED suffix.

However, I also see a log entry in the console about an unhandled rejection. This is coming from es6.promise. I dont understand why this is considered unhandled, considering that the middleware has dealt with the promise rejection. Is there something I'm supposed to be doing here?

Reduce boilerplate for reducers used with async actions

When you have a reducer that is used with async actions, there is often a lot of boilerplate code that is required. For example, most of my reducers I use async actions with have a switch very similar to this:

const defaultState = {
  isPending: undefined,
  isFulfilled: undefined,
  isRejected: undefined
}

switch (action.type) {
    case `${type}_PENDING`:
      return {
        ...state,
        ...defaultState,
        isPending: true
      };

    case `${type}_FULFILLED`:
      return {
        ...state,
        ...defaultState,
        isFulfilled: true,
        isSignedIn: true,
        token: action.payload
      };

    case `${type}_REJECTED`:
      return {
        ...state,
        ...defaultState,
        isRejected: true,
        error: action.payload
      };

   default: return state;
}

It would be nice to DRY up reducers that handle async actions and create a helper utility to do so.

Related to a comment on #33.

Return action object or promise

With the release of version 2.3.0, there has been some discussion at este/este#633 regarding what the middleware should return. The two sides of this discussion are:

  1. Return the action object
  2. Return the promise object

If the action object is returned, as it is currently implemented by @tomatau via #38, accessing the promise would look like:

const action = store.dispatch(asyncRequest())
action.promise.then(...)

The current implementation follows the dispatch() API described by the Redux documentation. The documentation specifies that it should return "the dispatched action".

The alternative would return the promise. For example:

store.dispatch(asyncRequest()).then(...)

This implementation would not follow the API as it is described by the Redux docs, but it could be more convenient, as some have argued. I am happy with the current implementation, but if we want to continue a discussion about changing it I would be open to that as well.

I upgraded to 3.0.0 from 2.4.0 started getting "Uncaught (in promise)" log errors.

This it not a bug as a suggestion for updated documentation or maybe a change to library that might lower the bar to users of library in some cases, and produce less surprise for some people.

Possibly my code is naive and everyone generally figures this out in minutes, unlike me who took way over an hour... :) In my case I dispatch an action with a promise for an API request, and deal with the Fulfilled/Rejected cases in my reducer so I had no promise chaining on the action dispatcher creation.

It may be good idea that you return your new promise with no catch handler and require users to do so wherever they dispatch actions (I have no strong opinion here). If you don't have a catch handler then "Uncaught (in promise)" to be logged to console (at least they do in Chrome with with the es6 promise shims I have tried).

I think a bit of documentation about ensuring that you catch() the result of dispatching an action would catch these log errors for people would help many I think. Maybe there is a way to default the library to not require in some case ?

old state sent to components

First, I don't know if it belongs to this project, but since this only happens in async operations I think it might. If this does not belong here please tell me where I can get help.

The issue:
I save the user's profile using an ajax call to my server. To do that I use a promise action called SAVE_PERSONAL_SETTINGS, and process the states in my reducer (in my code BEGIN SUCCESS ERROR).
In order to update my gui, I update the state in the BEGIN call so the new values are present (optimistic update) and shown on screen. And this is where stuff goes wrong. To make sure that my async code correctly updates the gui I added a 5 second delay to my server before sending back the response, so I can see what is going on, and this is what happens.

In my example I am changing the distance unit from mile to km. So the initial state in user.profile.personalInfo.distanceUnit is mile.

The reducer gets executed with _BEGIN, I update the state so user.profile.personalInfo.distanceUnit is now km. In the mean time the ajax request is running. The logs clearly show that next state contains km, however my components receive new state containing mile. As long as the ajax request is running nothing changes (so I basically see my old value pop up in my GUI again). As soon as the ajax request finishes loading all of the sudden my component again gets new state and gets the correct values. Mind you that in the _SUCCESS code in my reducer I do not touch that state again, as it was already set in _BEGIN.

Here is a screenshot of the logs

screen shot 2015-11-23 at 00 22 47

as you can see next state contains user.profile.personalInfo.distanceUnit = km

the next log is the log of the user.profile.personalInfo prop that gets set in my component, containing 'mile'.

the next log is the log of the user.profile.personalInfo prop that gets set in my component when the ajax request completes (5 seconds later), before logging SAVE_PERSONAL_SETTINGS_SUCCESS, containing 'km'.

I have no idea what is going on here. Maybe someone can help me out on how to debug this?

Supplying another action as type

So, I have a scenario where I want to redirect on a success callback. This can currently be done by firing of an action like so: dispatch(transitionTo('/route')) with the help of redux-react-router. I was thinking though that it would be convenient to be able to fire of actions/methods in response to a fulfilled/rejected promise in general.

Something along these lines:

export function login (email, password) {
  return {
    types: [LOGIN, loginSuccess(), LOGIN_ERROR],
    payload: {
      promise: authenticate(email, password)
    }
  };
}

The only problem I see with this, is that there would be no way of guaranteeing FSA compliancy by the subsequent actions fired manually by the developer.

Whats the general thought about this?

Extra properties from action are being stripped off

This is more just a question / observation.

I have a action creator that produces returns an action that looks like the following:

return {
        type: 'LOAD_WEATHER',
        payload,
        builder: 'weatherBuilderProvider'
      }

In my case, payload is a promise. I'm noticing that when the LOAD_WEATHER_FULFILLED action is sent my custom middleware, at that point, the builder property on my action has been stripped off. The only thing I see is the type: 'LOAD_WEATHER_FULFILLED and the payload with the resolved data. Is this by design? Or am I missing something?

Is there any way to pass other meta data around in actions? Ideally I'd like builder: 'weatherBuilderProvider' to be preserved such that my other custom middleware can inspect that property and use its value to know how to transform the resolved data.

Resolve thunks

With the release of 3.x and the inability to resolve thunks, the new API makes it more difficult to modify actions.

From @tomatau's comments on the original 3.0.0 PR:

how would I change the meta value between the pending and fulfilled actions? e.g. I want a special meta value only for the fulfilled but not pending or rejected.

We could either add the ability to resolve thunks or look into other options, such as DSL or a modify hook.

Problem: resolving thunks is not possible

// You can't do this
const actionCreator = () => ({
  type: 'BAR',
  payload: {
    promise: myAsyncAction().then((resolved) => 
      (action, dispatch) => {
        dispatch({ ...action, payload: { ..resolved }, meta: { broadcast: true } })
      }))
   }
});

Solution A: DSL

// example with regular meta that is dispatched in each action (pending, fulfilled/rejected)
{
  type: 'FOO',
  meta: {
    // ...
  }
}

// example with meta for each action
{
  type: 'BAR',
  meta: {
    pending: {
      // meta properties for pending action
    },
    fulfilled: {
      // meta properties for fulfilled action
    }
  }
}

Solution B: Modify Hook

A modify hook that can be supplied that sits between the resolved promise and dispatch so that the action can be modified.

const creator = () => ({
  type: 'BAR',
  payload: {
    promise: myAsyncAction(),
    modify: (action) => {
      // other logic?
      return { ...action, meta: { broadcast: true } }
    }
  },
})

Best pattern for chaining dependent actions (Question)

react: 0.14.3
react-router: 1.0.0
react-redux: 4.0.0
redux: 3.0.4
redux-thunk: 1.0.0
redux-promise-middleware: 2.2.4

I have everything working together great, but I'm reaching a point in development where it would be beneficial to start chaining a series of actions together. I know this has been covered in the README, #8, #24, and lots of other places, but I simply can't get any of them to work, so I must be going about it completely wrong.

Here's an example use case:

  1. Create - creates a new record via async service call, store in {record: {}} state
  2. Start - wait for the record to be created and then update the record in state to start a process
  3. Redirect - redirect to a new page beginning the process

The Create and Start are individual actions that I'd like to use in a single new action if the user knows that they want to start the process when they create the record.

actions.jsx

export function create(name) {
    return {
        type: CREATE,
        payload: {
            promise: axios.post(`${apiRoot}/create`, {name: name})
        }
    }
}

export function start() {
    return {
        type: START,
        payload: {
            promise: Promise.resolve()
        }
    }
}

reducer.jsx

        case `${CREATE}_PENDING`:
        ...
        case `${CREATE}_SUCCESS`:
        ...
        case `${CREATE}_ERROR`:
        ...
        case `${START}_PENDING`:
        ...
        case `${START}_SUCCESS`:
        ...
        case `${START}_ERROR`:

Individually, both actions (action creators) do exactly what I want them to do. However, I never seem to be able to get the second action to dispatch after the first one has finished.

I won't list all of the things I've tried, but here's an example of a failed attempt from #24.

export function createAndStart(name) {

    return (dispatch) => {
        dispatch({
            type: CREATE_AND_START,
            payload: {
                promise: new Promise((resolve, reject) => {
                    dispatch(create(name))
                        .then(() => {
                            dispatch(start());
                        })
                        .then(() => {
                            dispatch(push('/start'));
                        })
                })
            }
        });
    };
}

In this example, I see the createInspection action dispatched. I see it flow through each reducer state _PENDING then _SUCCESS, but I never see anything fall into the next .then()

I'm certain that I'm doing something stupid, but nothing has worked for me. The example in the README fails for me initially because dispatch isn't the first argument I see, action is. But even if I change that, the second action never gets dispatched.

bad

Promise.resolve((dispatch, getState) =>

good

Promise.resolve((action, dispatch, getState) =>

I appreciate any help that folks can offer. Thank you!

Make actions returned FSA compliant

I think it would be best to make sure the actions returned and accepted by the middleware follow a standard. Right now, the actions dispatched from the promise are FSA compliant, but the types array used in the action creators is not a part of the standard. Need to figure out how to handle that.

One thought is to just have one type instead of an array and just append _FULFILLED and _REJECTED to that type.

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.