1 | [![npm version](https://badge.fury.io/js/%40shoutem%2Fredux-io.svg)](https://badge.fury.io/js/%40shoutem%2Fredux-io)
|
2 |
|
3 | redux-io
|
4 | ====================
|
5 |
|
6 | `redux-io` is library for data management of network data in `redux` and ease of data use in `react`. Consists of
|
7 | middleware, reducers, action creators and helpers that provide:
|
8 |
|
9 | - [JSON-API](http://jsonapi.org/)
|
10 | - normalized data in `redux`
|
11 | - async CRUD operations with `redux` actions
|
12 | - optimistic updates of redux
|
13 | - denormalizing data for easier use in `react`
|
14 | - data caching and supported compatibility with `reselect`
|
15 | - simple and expandable network resource configuration
|
16 | - data status, error handling and monitoring operations
|
17 | - [SOON] JSON payloads with normalizr schemas
|
18 |
|
19 | ## Motivation
|
20 |
|
21 | Redux is a great library, but it’s not a framework. It’s based on simple concepts that enable the freedom to build,
|
22 | but without clear patterns. But how are you going to manage network data and organize it in store, make async requests,
|
23 | normalize data, handle errors, cache data, monitor data statuses? At Shoutem we developed platform based on `react`
|
24 | ecosystem in which we use `redux` for managing data. We learned a lot about problems and discovered practices that give
|
25 | answers to above questions. All that is used in development of `redux-io` library that we use everyday and enables us
|
26 | powerful data management.
|
27 |
|
28 | ## Documentation
|
29 |
|
30 | - [API Reference](https://github.com/shoutem/redux-io/tree/develop/docs/api)
|
31 |
|
32 | ## Getting started
|
33 |
|
34 | Install `redux-io`:
|
35 |
|
36 | ```
|
37 | $ npm install --save @shoutem/redux-io
|
38 | ```
|
39 |
|
40 | Import middlewares `apiMiddleware`, `apiStateMiddleware` and add them to your `createStore` in `applyMiddleware`
|
41 | function from `redux`. We are internally using [`redux-api-middleware@2.0.0-beta.1`]
|
42 | (https://github.com/agraboso/redux-api-middleware/tree/next) as library for async network requests,
|
43 | so that is the reason you need to add both middlewares respectively.
|
44 |
|
45 | Example:
|
46 |
|
47 | ```js
|
48 | import { applyMiddleware, compose, createStore } from 'redux';
|
49 | import { apiMiddleware } from 'redux-api-middleware';
|
50 | import { apiStateMiddleware } from '@shoutem/redux-io';
|
51 | import createLogger from 'redux-logger';
|
52 | import thunk from 'redux-thunk';
|
53 | import reducer from './reducers'
|
54 |
|
55 | const logger = createLogger();
|
56 |
|
57 | const store = createStore(
|
58 | reducer,
|
59 | applyMiddleware(thunk, apiMiddleware, apiStateMiddleware, logger)
|
60 | );
|
61 | ```
|
62 |
|
63 | And you are ready to start organizing state, configuring network resources, manage data with actions and use them in
|
64 | react components. Next section gives short intro in usage of redux-io in most common use cases.
|
65 |
|
66 | ## Usage
|
67 |
|
68 | For example, you have [json-api](http://jsonapi.org/) API endpoint that enables you to fetch items of type `acme.items`.
|
69 | You want to get most popular items, so endpoint `http://api.test.com/items?sort=relevance` returns you list of items
|
70 | sorted by popularity:
|
71 |
|
72 | ```json
|
73 | {
|
74 | "data": [
|
75 | {
|
76 | "type": "acme.items",
|
77 | "id": "1",
|
78 | "attributes": {
|
79 | "name": "Rocket",
|
80 | }
|
81 | },
|
82 | {
|
83 | "type": "acme.items",
|
84 | "id": "2",
|
85 | "attributes": {
|
86 | "name": "Helmet",
|
87 | }
|
88 | },
|
89 | ...
|
90 | ]
|
91 | }
|
92 | ```
|
93 |
|
94 | First you want to configure where fetched data will be placed in your state. Use `storage` as a normal reducer to define
|
95 | where in your state are instances of objects, and `collection` reducer to set place in the state for list holding ids
|
96 | of items that are popular. You are probably asking why do you need those two reducers, but idea is to keep data in redux
|
97 | state normalized. Normalization instances needs to be in one place in the state. However, you can reference it from
|
98 | mutliple parts in the state.
|
99 |
|
100 | ```js
|
101 | import { storage, collection } from `redux-api-state`;
|
102 |
|
103 | combineReducers({
|
104 | items: storage('acme.items'),
|
105 | popularItems: collection('acme.items', 'popularItems'),
|
106 | })
|
107 | ```
|
108 |
|
109 | After that, you only need to dispatch `find` action to fetch popular items from API. Find action is based on
|
110 | redux-api-middleware, and only needs to additional params `schema`, and `tag`. Schema defines in which `storage` will
|
111 | fetched data end up, and `tag` defines which `collection` will fetch ids of objects.
|
112 |
|
113 | ```js
|
114 | import { find } from `redux-api-state`;
|
115 |
|
116 | const config = {
|
117 | endpoint: 'http://api.test.com/items?sort=relevance',
|
118 | headers: {
|
119 | 'Content-Type': 'application/vnd.api+json',
|
120 | },
|
121 | };
|
122 |
|
123 | dispatch(find(config, 'acme.items', 'popularItems'));
|
124 | ```
|
125 |
|
126 | Upon dispatch, `find` will configure action by redux-api-middleware specification, and redux-api-middleware will
|
127 |
|
128 | 1. Dispatch REQUEST action
|
129 | 2. Make GET request on http://api.test.com/items?sort=relevance
|
130 | 3. If request is successful dispatch SUCCESS action
|
131 |
|
132 | You can see that it is by `redux-api-middleware` [lifecycle](www.test.com). After which redux-api-state will listen on
|
133 | SUCCESS action and will act as:
|
134 |
|
135 | 1. Validate SUCCESS action
|
136 | 2. Unpack payload
|
137 | 3. For each item in data will dispatch OBJECT_FETCHED
|
138 | 4. Storage reducer will listen for OBJECT_FETCH and add it into map in state
|
139 | 5. Dispatch COLLECTION_FETCHED
|
140 | 6. Collection reducer will listen for COLLECTION_FETCHED and add items id into list
|
141 | 7. Call next(action) for success action from redux-api-middleware
|
142 |
|
143 | Storage reducer only adds an item if action is valid and schema value is equal to the action's schema. Collection
|
144 | reducer performs the same, but checks also `tag` value. That enable us to have multiple collections of objects, but only
|
145 | one storage with instances of objects. Here is the state after app finished fetching:
|
146 |
|
147 | ```js
|
148 | state: {
|
149 | items: {
|
150 | 1: {
|
151 | "type": "acme.items",
|
152 | "id": "1",
|
153 | "attributes": {
|
154 | "name": "Rocket",
|
155 | }
|
156 | },
|
157 | 2: {
|
158 | "type": "acme.items",
|
159 | "id": "2",
|
160 | "attributes": {
|
161 | "name": "Helmet",
|
162 | }
|
163 | },
|
164 | },
|
165 | popularItems: [1, 2, 3, ... ],
|
166 | }
|
167 | ```
|
168 |
|
169 | ## Tests
|
170 |
|
171 | ```
|
172 | $ npm install && npm test
|
173 | ```
|
174 |
|
175 | ## Acknowledgements
|
176 |
|
177 | The package is based on concepts from [Željko Rumenjak](https://github.com/zrumenjak).
|