Redux Service Middleware
raw JSON →The `inst-redux-service-middleware` package provides a Redux middleware designed to standardize and simplify the process of calling external 'service' objects from within Redux actions. Instead of writing bespoke thunks or sagas for every asynchronous service interaction, this middleware allows developers to register service instances and invoke their methods via a consistent, predefined action shape. This approach reduces boilerplate and promotes a cleaner separation of concerns by centralizing service invocation logic. Currently at version 22.17.0, the package appears to be actively maintained, offering a mature solution for abstracting side effects related to service calls. Its primary differentiation lies in providing a generic mechanism for calling any registered service method, which can be particularly useful for integrating various API clients or utility functions without deep Redux-specific implementations for each.
Common errors
error Error: Service 'myKey' not found in middleware configuration. ↓
action.payload.service exactly matches one of the keys provided to createServiceMiddleware. error TypeError: Cannot read properties of undefined (reading 'methodName') ↓
method name in your action payload is spelled correctly and exists on the registered service object. error A state mutation was detected between dispatches ↓
..., Object.assign(), Immer.js). Warnings
gotcha The `serviceMiddleware.CALL_SERVICE` constant must be used as the `type` property of the action object when dispatching service calls. Forgetting this or using a different string will prevent the middleware from intercepting the action. The `serviceMiddleware` object itself is a named export, and `CALL_SERVICE` is a property on it. ↓
breaking When upgrading Redux itself to version 4.0 or higher, the signature for custom Redux middleware was simplified. While `inst-redux-service-middleware` should be compatible, any custom middleware you've written might need to be updated to the `({ getState, dispatch }) => next => action` signature. ↓
gotcha Services registered with `createServiceMiddleware` must be plain JavaScript objects with methods. Ensure that these methods do not directly mutate the Redux state, as Redux strictly enforces immutability. Side effects should produce new state via dispatched actions. ↓
gotcha Managing complex asynchronous flows (e.g., sequential calls, debouncing, cancellations) purely through the basic action shape of this middleware can become cumbersome. For advanced side effect management, consider combining this middleware with solutions like `redux-saga` or `redux-observable`, or leveraging `redux-thunk` if it fits the async pattern. ↓
gotcha As the JavaScript ecosystem shifts towards ES Modules (ESM), importing CommonJS (CJS) modules, or dealing with hybrid packages, can lead to unexpected import errors (e.g., 'Named export not found' or issues with `require`). While this package likely supports CJS, modern bundlers and Node.js environments often default to ESM. ↓
Install
npm install inst-redux-service-middleware yarn add inst-redux-service-middleware pnpm add inst-redux-service-middleware Imports
- createServiceMiddleware wrong
const createServiceMiddleware = require('inst-redux-service-middleware').createServiceMiddleware;correctimport { createServiceMiddleware } from 'inst-redux-service-middleware'; - serviceMiddleware wrong
const serviceMiddleware = require('inst-redux-service-middleware'); // Incorrectly assumes default export or direct access to propertiescorrectimport { serviceMiddleware } from 'inst-redux-service-middleware'; - CALL_SERVICE wrong
import { CALL_SERVICE } from 'inst-redux-service-middleware';correctimport { serviceMiddleware } from 'inst-redux-service-middleware'; // ... then use serviceMiddleware.CALL_SERVICE
Quickstart
import { createStore, applyMiddleware } from 'redux';
import { createServiceMiddleware, serviceMiddleware } from 'inst-redux-service-middleware';
// A simple reducer
const rootReducer = (state = { data: null, error: null }, action) => {
switch (action.type) {
case 'FETCH_SUCCESS':
return { ...state, data: action.payload, error: null };
case 'FETCH_ERROR':
return { ...state, error: action.payload, data: null };
default:
return state;
}
};
// Define a simple service
const myService = {
async fetchData(id) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Service error:', error);
throw error; // Re-throw to be caught by the action creator if needed
}
},
getGreeting(name) {
return `Hello, ${name}!`;
}
};
// Create the service middleware and register services
const serviceMid = createServiceMiddleware({
api: myService,
greeter: myService
});
// Create the Redux store with the middleware
const store = createStore(rootReducer, applyMiddleware(serviceMid));
console.log('Initial state:', store.getState());
// Dispatch an action to call a service method
store.dispatch({
type: serviceMiddleware.CALL_SERVICE,
payload: {
service: 'api',
method: 'fetchData',
args: [1]
},
meta: {
onSuccess: (data) => ({ type: 'FETCH_SUCCESS', payload: data }),
onError: (error) => ({ type: 'FETCH_ERROR', payload: error.message })
}
});
// Example of another service call
store.dispatch({
type: serviceMiddleware.CALL_SERVICE,
payload: {
service: 'greeter',
method: 'getGreeting',
args: ['World']
},
meta: {
onSuccess: (greeting) => console.log('Greeting:', greeting),
onError: (error) => console.error('Greeting error:', error)
}
});