Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Necessary to include request in middleware? #29

Open
brandondurham opened this issue May 3, 2017 · 2 comments
Open

Necessary to include request in middleware? #29

brandondurham opened this issue May 3, 2017 · 2 comments

Comments

@brandondurham
Copy link

I have all of my requests set up to fire within my Sagas. I noticed in your example you use socket.emit and then the response to create a Redux action.

Is it necessary to have the socket.emit request in the middleware for redux-optimistic-ui to work? If it isn’t necessary, are there any examples that illustrate that?

@dyst5422
Copy link

No!

Ok, so I fumbled around with this for a while. Here is what came up with in case anyone else happens upon this. I completely skip the middleware. After all, Thunks, Sagas etc are just processed in middleware anyways (so REALLY I just happen to be Thunk as my middleware piece and get clever in my action)

I happen to be using Thunk's to do this, but any asyncronous dispatching should be fine

let nextTransactionID = 0;
async function thunkAction(): ThunkAction<Promise<void>, IReduxState, undefined> {
  const transactionID = nextTransactionID++;

  try {
    // Optimistic dispatch
    dispatch({ ...newDataActionCreator(optimisticData),
      meta: { optimistic: { type: BEGIN, id: transactionID } },
    });
    const response = await fetch(...);

    const realData = await response.json().data;
    // Ok, now lets rewrite with our real data
    // note that the state after optimistic dispatch is the starting point for this dispatch
    // Either have a different action here, or ensure that applying the action with the real data AFTER
    // the action with the optimistic data gives you your desired state
    dispatch({ ...newDataActionCreator(realData),
      meta: { optimistic: { type: COMMIT, id: transactionID } },
    });
  } catch(err) {
    // Here, the state is reverted to before the optimistic dispatch before applying the next dispatch
    dispatch({ ...errorActionCreator(new Error('Fetch failed')),
          meta: { optimistic: { type: REVERT, id: transactionID } },
    });
  }

@Birkbjo
Copy link

Birkbjo commented Oct 11, 2017

I know this is an old issue, but I will show another example for good measure.
This is currently how I do it in my app, using redux-observables. This combination has been wonderful to use, so thanks a lot for this library!
The great thing about this approach is that the middleware handles the IDs, while the epics only really decide if it should be committed or reverted.

//ReduxOptimisticMiddleware.js
import { BEGIN, COMMIT, REVERT } from "redux-optimistic-ui";

//All redux action types that are optimistic have the following suffixes
const _SUCCESS = "_SUCCESS";
const _ERROR = "_ERROR";

//Each optimistic item will need a transaction Id to internally match the BEGIN to the COMMIT/REVERT
let nextTransactionID = 0;

export default store => next => action => {
    // FSA compliant
    const { type, meta, error, payload } = action;

    // Ignore actions without isOptimistic flag
    if (!meta || !meta.isOptimistic) return next(action);

    const isSuccessAction = type.endsWith(_SUCCESS);
    const isErrorAction = type.endsWith(_ERROR);
    //Response from server, handled in epic-middleware
    if (isSuccessAction || isErrorAction) {
        return next(action);
    }

    // Now that we know we're optimistically updating the item, give it an ID
    let transactionID = nextTransactionID++;
    // Sending to server; extend the action.meta to let it know we're beginning an optimistic update
    return next(
        Object.assign({}, action, {
            meta: { optimistic: { type: BEGIN, id: transactionID } }
        })
    );
};
//epics.js
const editApp = action$ =>
    action$.ofType(actions.APP_EDIT).concatMap(action => {
        const { app, data } = action.payload;
        return api
            .updateApp(app.id, data)
            .then(resp =>
                actionCreators.commitOrRevertOptimisticAction(
                    actionCreators.editAppSuccess(app, data),
                    action
                )
            )
            .catch(error =>
                actionCreators.commitOrRevertOptimisticAction(
                    actionCreators.actionErrorCreator(
                        actions.APP_EDIT_ERROR,
                        error
                    ),
                    action
                )
            );
    });
export default combineEpics(editApp);
//actionCreators.js
const optimisticActionCreator = action => ({
    ...action,
    meta: { ...action.meta, isOptimistic: true }
});

export const commitOrRevertOptimisticAction = (
    action,
    transaction,
    error = false
) => {
    if (action.error) {
        error = true;
    }
    let transactionID = transaction;
    if(transaction && transaction.meta && transaction.meta.optimistic) {
        transactionID = transaction.meta.optimistic.id;
    }
    return {
        ...action,
        meta: {
            ...action.meta,
            optimistic: error
                ? { type: REVERT, id: transactionID }
                : { type: COMMIT, id: transactionID }
        }
    };
};

//This is what my actionCreators will look like
export const optimisticEditApp = (app, data) =>
    optimisticActionCreator(
        actionCreator(actions.APP_EDIT)({
            app,
            data
        })
    );

That's it! All I do to have an action being optimistic is decorating it with optimisticActionCreator. In my views I can just do dispatch(optimisticEditApp).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants