Custom Action Types
The name 'plugins' may seem intimidating, but don't be worried. Plugins are reducers that you can reuse for any resource slice. If you know how to write a reducer, then you know how to write a plugin.
You define plugins for each resource type when you call
resourceReducer
. The second argument to that function is an options
option, and within it you can pass plugins
as an array:import resourceReducer from 'redux-resource';
import somePlugin from './some-plugin';
import anotherPlugin from './another-plugin';
export default resourceReducer('books', {
plugins: [somePlugin, anotherPlugin]
});
A plugin is a function that with the following signature:
(resourceType, options) => reducerFunction
The return value,
reducerFunction
, is also a function. This returned function has the same signature as a Redux reducer:(previousState, action) => newState
where
state
is the value of the state after running it through the built-in reducer and action
is the action that was dispatched.The simplest plugin then (which doesn't do anything), would look like this:
function myPlugin(resourceType, options) {
return function(state, action) {
return state;
}
}
If you prefer using arrow functions, you might choose to write this like so:
const myPlugin = (resourceType, option) => (state, action) => state;
This plugin isn't very exciting, so let's look at more realistic examples.
Let's build a plugin that lets a user select resources. The code for this plugin looks like this:
import { setResourceMeta } from 'redux-resource';
import myActionTypes from './my-action-types';
export default function(resourceType, options) {
return function(state, action) {
// Ignore actions that were dispatched for another resource type
if (action.resourceType !== resourceType) {
return state;
}
if (action.type === myActionTypes.SELECT_RESOURCES) {
return {
...state,
meta: setResourceMeta({
resources: action.resources,
meta: state.meta,
newMeta: {
selected: true
},
initialResourceMeta: options.initialResourceMeta
})
};
} else if (action.type === myActionTypes.UNSELECT_RESOURCES) {
return {
...state,
meta: setResourceMeta({
resources: action.resources,
meta: state.meta,
newMeta: {
selected: false
},
initialResourceMeta: options.initialResourceMeta
})
};
} else {
return state;
}
}
}
You would then use this plugin like so:
import { createStore, combineReducers } from 'redux';
import { resourceReducer } from 'redux-resource';
import selectResources from './plugins/select-resources';
let store = createStore(
combineReducers({
books: resourceReducer('books', {
plugins: [selectResources]
}),
})
);
You can write plugins that can be customized per-slice by taking advantage of the fact that the
resourceReducer
's options are passed into plugins. For instance, if you had a plugin like the following:export default function customizablePlugin(resourceType, options) {
return function(state, action) {
if (options.useSpecialBehavior) {
// Perform a computation
} else {
// Do some other computation here
}
};
}
then you could trigger the special behavior by passing
useSpecialBehavior: true
as an option to resourceReducer
:import resourceReducer from 'redux-resource';
import customizablePlugin from './customizable-plugin';
export default resourceReducer('books', {
plugins: [customizablePlugin],
useSpecialBehavior: true
});
If this API isn't to your liking, then you can also just wrap the plugin itself in a function, like so:
export default function(pluginOptions) {
return function customizablePlugin(resourceType, options) {
return function(state, action) {
if (pluginOptions.useSpecialBehavior) {
// Perform a computation
} else {
// Do some other computation here
}
};
};
}
which would be used in the following way:
import resourceReducer from 'redux-resource';
import customizablePlugin from './customizable-plugin';
export default resourceReducer('books', {
plugins: [
customizablePlugin({ useSpecialBehavior: true})
]
});
You may dislike this approach due to the tripley-nested functions. That's fine, because either way works. Use the version that makes the most sense to you.
Because plugins are so similar to reducers, you can use a
switch
statement and support multiple action types within each plugin. This is usually a good thing, but be mindful of keeping each plugin limited to a single responsibility.For example, in the above example of a plugin for selecting resources, it supports two Action types – one for selection, and one for deselection. This plugin encapsulates that one responsibility, and it isn't responsible for any other Action types.
We recommend having a plugin for each distinct responsibility.
Last modified 3yr ago