A Store of Actions for optimistic updates is a goo

2020-05-20 07:26发布

问题:

I've been working with optimistic updates in a React+Flux application and saw two things:

  1. What happens if a user attempts to close the window when exists some uncompleted actions. For example in Facebook, a message appears in the wall even if wasn't really persisted (this is what optimistic updates does, a more responsive application for the user). But, if a user post in the wall and immediately close the application (on logout or window close), the post could fail and he would not be alerted.
  2. I don't like the idea of Stores managing his own entities (for example messages) and the situation of the action triggered for persiste a message (loading, succesfull, failed?). It mixes things.

So I work on this and create an ActionStore to manage the state of the actions triggered by the components. Here is the source code and here is a live demo.

It works more or less like this:

  1. The root of the components hierarchy (container in redux) fetch the nextId of a new action and pass it to his childs like props (this is ugly).
  2. A child component fires an action: it keeps the actionId for ask it to the store after and call the action creator.
  3. The action creator creates a new Action and returns a function to the middleware.
  4. The function returned from the Action creates a new Promise with the API call and dispatches an action of type XX_START.
  5. The ActionStore listen to the XX_START action and saves it.
  6. The child component receives the new state and find the action with the saved id and ask it the current situation: loading, successful or failed.

I've done this mainly for separate the state of the "entities" from the state of the actions, but allows retrigger actions with the same payload too (this could be useful when we receive 500 response status if the server is temporarly down or if the user loose signal).

Also, having an store of actions allows to easily ask if they are pending actions before the user logout or close the window.

Note: I'm working with a Single Application Page web app against a Rest API, I'm not think to use this on server-side rendering

It's a viable option create a ActionStore or I'm breaking some Redux/Flux foundations? This could end the posibility of use React Hot Reloading and Time traveling?

You should answer with no mercy, I've probably done a bunch of ugly things but I'm learning React/Redux.

回答1:

To the future readers who might be a little bewildered: we don't call these functions “stores” anymore: we call them reducers. So the question is about a reducer remembering actions.

Personally I don't like the approach you're suggesting there. You're putting logic into actions and using inheritance for this. On the contrary, Redux prescribes actions to be plain objects without logic. They shouldn't “know” how to update some state, and they shouldn't hold it—instead, they should describe what happened.

I think the approach for implementing optimistic updates in Redux is similar to how you'd implement Undo in Redux, which in turn is inspired by Elm architecture. The idea is that you should write a function that takes a reducer (and some options) and returns a reducer:

function optimistic(reducer) {
  return function (state, action) {
    // do something?
    return reducer(state, action);
  }
}

You'd use it just like a normal reducer:

export default combineReducers({
  something: optimistic(something) // wrap "something" reducer
})

Right now it just passes the state and action to the reducer it wraps, but it can also “remember” actions:

function optimistic(reducer) {
  return function (state = { history: [] }, action) {
    // do something with history?
    return {
      history: [...state.history, action],
      present: reducer(state.history[state.history.length - 1], action)
    };
  }
}

Now it accumulates all actions in the history field.

Again, this by itself is not very useful, but you can probably see how you can implement such higher order reducer that:

  • keeps track of pending async actions by their IDs and starts recording the history when it sees an action with status: 'request';
  • when it receives an action with status: 'done', resets the history;
  • when it receives an action with status: 'fail', adjusts the history to not include the initial action with status: 'request' and re-runs the reducer to re-calculate the present state as if the request never happened.

Of course I haven't provided the implementation, but this should give you an idea of how to implement optimistic updates in a Redux way.

Update: as per the OP's comment, redux-optimist seems to do exactly that.