redux-offline ignore middlewares when executing commit or rollback actions - redux

As shown at https://github.com/redux-offline/redux-offline/pull/178#issuecomment-408795302, we are trying to use with redux-offline a middleware that can dispatch new actions after their counter-parts commit or rollback are executed. Point is that these ones are not dispatched, and after debugging it, we have found that when dispatching the initial action the middleware is being used as the dispatch() function (probably due to how redux composeEnhancers() and applyMiddleware() function works, since they are chaining the functions), but when the commit action is dispatched, it's done using directly the store dispatch() method, so no middleware is being executed at all.
We are not fully sure if it's a fault on our side regarding redux-offline configuration, or a bug in redux-offline itself... Our store configuration is like this:
import { applyMiddleware, compose, createStore } from 'redux'
import reduxOfflineThunkMiddleware from './thunk-middleware'
import rootReducer from '../../reducers'
import { createUser } from '../../actions/user'
const initialState = {}
const windowCompose = (typeof window !== 'undefined') && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
const composeEnhancers = windowCompose || compose
const store = createStore(
rootReducer,
initialState,
composeEnhancers(
applyMiddleware(reduxOfflineThunkMiddleware({ createUser })),
offline()
)
)

Yes, both offline and applyMiddleware are "store enhancers". When you call store.dispatch, the action sequence will be:
Processed by all the middlewares in the middleware pipeline
Processed by offline
Handled by the store itself
Because the offline enhancer is after the applyMiddleware enhancer, any actions that it dispatches internally will never go through the middleware pipeline.

Related

Is it possible to manually dispatch thunk state in createAsyncthunk

Hey fellow programmers,
Been having fun learning react-redux lately, but I do have one question that bothers me.
My understanding is that, by using createAsyncThunk it will automatically generates action type constants. (pending, fulfilled, and rejected)
What I wanted to know is that is there any way to manually dispatch action type during createAsyncthunk , so that we can have more flexibility in our code.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId).then(
...
dispatch(fulfilled) // is this possible ?
).catch(
dispatch(rejected) // is this possible ?
)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
...,
extraReducers: {
// Add reducers for additional action types here, and handle loading state as needed
[fetchUserById.fulfilled]: (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
}
}
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
The point of createAsyncThunk is that it generates those action types, _and dispatches them for you automatically. You definitely do not need to dispatch(fulfilled()) yourself, because that's what createAsyncThunk does for you - you just need to return a promise that either resolves or reject, and it dispatches the fulfilled/rejected action types based on that.
You do get access to thunkAPI.dispatch, so you can dispatch other actions if necessary, but you don't need to worry about the fulfilled/rejected actions yourself.

What is the correct way to combine redux-thunk and redux-batched-actions?

What is the correct way to plug redux-batched-actions into my existing Redux store? I am completely confused by the Redux middleware API.
Currently I am using redux-thunk and redux-little-router.
Here is the code source that creates my Redux store:
import { createStore, applyMiddleware, compose, combineReducers } from 'redux'
import thunk from 'redux-thunk'
import { routerForBrowser } from 'redux-little-router'
import reducers from './store'
import routes from './routes'
const { reducer, middleware, enhancer } = routerForBrowser({ routes })
// Combine all reducers and instantiate the app-wide store instance
const allReducers = combineReducers({ ...reducers, router: reducer })
// Build middleware (if devTools is installed, connect to it)
const allEnhancers = (window.__REDUX_DEVTOOLS_EXTENSION__
? compose(
enhancer,
applyMiddleware(thunk, middleware),
window.__REDUX_DEVTOOLS_EXTENSION__())
: compose(
enhancer,
applyMiddleware(thunk, middleware)))
// Instantiate the app-wide store instance
const initialState = window.initialReduxState
const store = createStore(
allReducers,
initialState,
allEnhancers
)
The redux-batched-actions documentation exposes two usages: enableBatching and batchDispatchMiddleware. Which one should I use in my case?
Answering my own question after the return of my expedition into the fabulous source code of redux, redux-thunk, redux-batched-actions, ...
The correct way to do it seems to be using batchDispatchMiddleware, like this:
import { batchDispatchMiddleware } from 'redux-batched-actions'
// ...
const allEnhancers = (window.__REDUX_DEVTOOLS_EXTENSION__
? compose(
enhancer,
applyMiddleware(batchDispatchMiddleware, thunk, middleware),
window.__REDUX_DEVTOOLS_EXTENSION__())
: compose(
enhancer,
applyMiddleware(batchDispatchMiddleware, thunk, middleware)))
Note: I don't know if I could dispatch batched thunks, though. I don't do that in my current application. Use at your own risk!

Redux-thunk async actions: Use custom middleware for async actions

I am using redux-thunk for async actions and babel-polyfill for promises. I get the following error: Error: Actions must be plain objects. Use custom middleware for async actions.
I solved this problem by including redux-promise in my middleware. I am not sure why I had to use redux-promise to resolve this issue because all examples in Redux docs use babel-polyfill. Should I continue using redux-promise or I might have some issues with babel-polyfill?
babel-polyfill is included in my app entry point:
import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './components/App.jsx';
import store from './store.jsx';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.querySelector('.container'));
UPDATE:
So I checked just in case if I have redux-thunk installed. It is in my package.json. Here is my store.js
import thunkMiddleware from 'redux-thunk';
import promise from 'redux-promise'
export default store = createStore(
rootReducer,
defaultState,
applyMiddleware(
thunkMiddleware,
promise
)
);
Here is my async action in action.js:
function receiveStates(json) {
return {
type: RECEIVE_STATES,
states: json.states,
};
}
export function fetchStates(uuid) {
return dispatch =>
fetch(`https://my-api.com/session/${uuid}`)
.then(response => response.json())
.then(json => dispatch(receiveStates(json)));
}
Here is how I call action from component:
fetchStates(sessionID) {
this.props.dispatch(fetchStates(sessionID));
}
# I bind this function in component's constructor
this.fetchStates = this.fetchStates.bind(this);
And finally, this is my reducer:
function statesReducer(state = null, action) {
switch (action.type) {
case RECEIVE_STATES:
return { ...state, states: action.states };
default:
return state;
}
}
I think your error could be caused by:
You are not returning a thunk (a function returning a function of dispatch) from your action creator, so that the Thunk middleware doesn't catch it (maybe you are returning a promise instead?)
You have not installed redux-thunk as suggested by dzeno
I suggest that you install the redux-logger middleware and add it to your store middlewares as the last one, removing the promise middleware which you would not need if returning a thunk.
In this way all the actions are logged in console (previous state, current action, next state) and you can debug what action object type you are returning that is not "digested" by the thunk middleware.
It sounds like you haven't installed / setup redux-thunk yet.
You install the npm package in the usual way:
npm install --save redux-thunk
And here is an example of applying the redux-thunk middleware
getStore.js
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const getStore = ({combined, initial}) =>{
var store = createStore(combined, initial, applyMiddleware(thunk))
return store
}
export{
getStore
}

Using redux-loop with thunk action creators

I'm using redux-loop to call action creators from my reducers. This works great normally.
However, I'm also using thunk for some of my action creators. If I take a regular action creator and convert it to a thunk, it's no longer usable in redux-loop.
Is there a way to call thunks from redux-loop in reducers?
I recommend that you pass applyMiddleware prior to install in your enhancers.
createStore(reducer, initialState, compose(
applyMiddleware(thunk),
install()
));
applyMiddelware will catch actions passed to store.dispatch() before redux-loop tries to dispatch them. Right now for the next version of redux-loop I'm planning to have install() accept the other enhancers for the store before applying its own modifications so that this doesn't end up being a problem.
I had no luck combining redux-loop and redux-thunk as-is. The problem is that if you you call applyMiddleware(thunk) first and redux-loop's install() afterwards, actions dispatched by thunks will not have their effects evaluated (because the dispatch passed to thunks by the middleware isn't enhanced by redux-loop yet); while if you swap the two around, effects are not able to dispatch thunks (because the version of dispatch redux-loop uses for effects is not enhanced with the thunk middleware).
To work around this problem, I needed to write the following pretty hacky store enhancer:
import { applyMiddleware, compose } from 'redux'
import { install } from 'redux-loop'
export default function loopWithThunk(createStore) {
let enhancedStore
const storeSaver = (createStore) => (reducer, initialState) => {
enhancedStore = createStore(reducer, initialState)
return enhancedStore
}
const thunkMiddleware = () => next => action => {
return (typeof action === 'function')
? action(enhancedStore.dispatch, enhancedStore.getState)
: next(action)
}
return compose(
storeSaver,
install(),
applyMiddleware(thunkMiddleware)
)(createStore)
}
You can use it like this:
const store = createStore(reducer, loopWithThunk)

redux-storage with redux-saga - not saving

I am trying to use both the redux-storage and redux-saga middleware for redux. Each middleware works great on its own, but when I apply redux-storage with redux-saga, the store does not load the prior state. I am adding the middleware as follows:
const combinedReducers = storage.reducer(combineReducers(reducers));
import createEngine from 'redux-storage-engine-reactnativeasyncstorage';
const engine = createEngine('storage-key');
const storageMiddleWare = storage.createMiddleware(engine);
const sagaMiddleWare = createSagaMiddleware(mySagas);
export const store = createStore(combinedReducers, applyMiddleware( sagaMiddleWare, storageMiddleWare));
const load = storage.createLoader(engine);
load(store).then(() =>
{
console.log("Loaded State");
console.log(store.getState());
}).done();
Middlewares execute in the order they're passed into applyMiddleware. Loading from storage should be before the saga middleware, eg:
const finalCreateStore = compose(
applyMiddleware(
storage.createMiddleware(engine),
createSagaMiddleware(sagas)
)
)(createStore)
D'oh! A bunch of 'EFFECT_TRIGGERED' Actions are emitted right away from redux-saga. These trigger in redux-storage a save event of an empty store, before the store has loaded. The solution is to add EFFECT_TRIGGERED to the blacklist when creating the redux-storage middleware like so:
const storageMiddleWare = storage.createMiddleware(engine, [= 'EFFECT_TRIGGERED']);

Resources