redux-shelf
v0.0.9
Published
Helper reducers and actions to take away your boilerplate code from both Data and Communication state types
Downloads
5
Readme
Redux Shelf
Redux Shelf is a library that provide a simple API to create and manage both Entity and Communication state of your application using Redux reducers, by managing for you all the boilerplate necessary to do that.
Influences
React applications contains several types of state wich we have to manage. James K. Nelson, on this great article, identify 5 types of them. On this library we are focused on two of the 5 types: Data state, that we prefer to call Entity state, and Communication state. Below you can find a summary of each type:
Data State
"Data state covers information which your application temporarily stores about the big wide world. That is, it covers your business data."
"Every piece of received Data has a type, and a selector which exactly specifies which data was received."
Communication State
"This type of state covers the seemingly simple yet somewhat thorny information which represents things like loading spinners and error messages."
"Communication state is the status of any not-yet-complete requests to other services."
"This means that all of the following are communication state:
- The type/selector for any Data you expect to receive
- The type, selector and expected change of any operations you have requested on Data
- The error messages for anything which didn’t go quite as planned."
With Redux Shelf you can reduce Entity and Communication state management boilerplate in your application, by using a simple and easy to learn API.
Installation
yarn add redux-shelf
or
npm install redux-shelf
Configuration
// reducers/index.js
import { combineReducers } from 'redux';
import { entities, communication } from 'redux-shelf';
export default (appReducers = combineReducers({
entities,
communication,
}));
API
Bellow you can find the description of the API provided by Redux Shelf library.
Entity
set(type, payload)
: Overrides the current state of an Entity.update(type, payload)
: Merge the current state of an Entity with new state.remove(type, selector)
: Remove a record of an Entity.idsOf(type)
: Returns the array of ids of an Entity type provided as parameter.contentOf(type, selector)
: Returns content object of an specific Entity record, identified by its type and selector provided as parameters.of(type, selector?)
: It's an alias foridsOf
andcontentOf
methods. When onlytype
parameter is given toof
method it behaves likeidsOf
call, while whenselector
parameter is also providedof
method will behave likecontentOf
call.
Note: By using Entity Actions API we're assuming that you'll
normalize Entity data on ids/content form. So, you must either use normalize
function
provided by library or use another one that works similarly (check Utils section).
Communication
starting(type, selector?)
: Sets communication status with theSTARTING
status for the given entity type and selector.done(type, selector?)
: Sets communication status with theDONE
status for the given entity type and selector.fail(type, selector|error, error)
: Sets communication status with theFAIL
status for the given entity type, selector and/or error.cancel(type, selector?)
: Sets communication status with theCANCEL
status for the given entity type and selector.of(type, selector?)
: Returns an object withloading
anderror
.
Utils
normalize(payload, key?)
: Normalizes a given payload to ids/content shape. Ifkey
parameter is not provided, the function will normalize the payload by id property, assuming that it has it. The valid values for payload parameter are: An object or an array of objects. If the value provided as payload parameter is invalid, the function will return a default normalized object{ ids: [], content: {} }
. See the examples below:
const payload = [{ id: 1, name: 'Product 1' }, { id: 2, name: 'Product 2' }];
console.log(normalize(payload, 'id'));
// console output
/*
{
ids: [1, 2],
content: {
1: { id: 1, name: 'Product 1' },
2: { id: 2, name: 'Product 2' },
},
}
*/
...
const payload = { id: 1, name: 'Product 1' };
console.log(normalize(payload));
// console output
/*
{
ids: [1],
content: {
1: { id: 1, name: 'Product 1' },
},
}
*/
...
const payload = [
{ identifier: 1, name: 'Product 1' },
{ identifier: 2, name: 'Product 2' },
{ id: 3, name: 'Product 2' },
true,
null,
42,
];
console.log(normalize(payload, 'identifier'));
// console output
/*
{
ids: [1, 2],
content: {
1: { identifier: 1, name: 'Product 1' },
2: { identifier: 2, name: 'Product 2' },
},
}
*/
Usage
// userActions.js
import { entities, communication, normalize } from 'redux-shelf';
// Here I assuming that you're using some middleware to handle
// asynchronous actions, for example, Redux Thunk
export function fetchUsers() {
return async (dispatch) => {
dispatch(communication.starting('users'));
try {
const url = 'endpoint_to_get_users_data';
const request = await fetch(url);
const payload = request.json();
dispatch(entities.set('users', normalize(payload)));
dispatch(communication.done('users'));
} catch (e) {
dispatch(communication.fail('users', e));
console.log(e);
}
};
}
...
// UserList.jsx
export const UserList = ({ loading, error, userIds }) => {
if (error) {
return <div>Failed to load users</div>
}
if (loading) {
return <div>Loading...</div>
}
return (
<div>
{userIds.map(userId => <UserItem key={userId} userId={id} />)}
</div>
);
);
export default connect(
({ entities, communication }) => {
const {
loading,
error,
} = communication.of('users');
const userIds = entities.idsOf('users');
return {
loading,
error,
userIds,
};
},
)(UserList);
...
// UserItem.jsx
export const UserItem = ({ name }) => (
<span>
{name}
</span>
);
export default connect(
({ entities }, { userId }) => {
const user = entities.contentOf('users', userId);
return {
name: user.name,
};
},
)(UserItem);
License
MIT