Skip to content

Commit 2a2bfd5

Browse files
Merge pull request #5 from aspect-apps/feature/improve-api
improving api
2 parents 3adf819 + 53e7f08 commit 2a2bfd5

File tree

4 files changed

+263
-231
lines changed

4 files changed

+263
-231
lines changed

docs/README.md

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,48 @@
22

33
Install with npm:
44
```
5-
npm install redux-persist-machine --save
5+
npm install redux-persist-machine
66
```
77

88
## Usage
99

1010
Import with:
1111

1212
```js
13-
import { persistMiddleware, createPersistMachine } from "redux-persist-machine";
13+
import { createPersistMachine } from "redux-persist-machine";
1414
```
1515

1616
### Setup
1717

18-
There are a couple of things you need to get started. Firstly add the `persistMiddleware` to your redux middleware.
18+
There are a couple of things you need to get started. Firstly add the return of `createPersistMiddleware` to your redux middleware.
1919

20-
The middleware parameters are a save and load method, you don't have to write these yourself, they are [available as separate packages below](#providers).
20+
The `createPersistMiddleware()` parameters are:
21+
- the structure of your store and use arrays to define what values you want the middleware to persist
22+
- a save method
23+
- a load method
24+
- (optional) whether to enable debug or not
25+
26+
The structure of your store consist of an object that takes some objects, and each object has to follow the following schema:
27+
28+
- The key of each value in the persist object should match your store shape, a nested reducer would be defined as such: `data.device`.
29+
- In the `values` key, you can customize which values the package will keep track of. If nothing is provided to the `values` field, all fields will be saved.
30+
- A `key` is required to be defined for each persisted reducer, this will be used as the async storage key, it keeps a static map of your data which is independent of its shape, which is useful as your application grows.
31+
- An optional key of `action` can be defined to explicitly declare which action type should trigger a load of that reducer, without this value each load type is generated automatically from the state shape. e.g. to load `"data.device"` fire `LOAD_DATA_DEVICE`, to load `"subscriptionOrders"` fire `LOAD_SUBSCRIPTION_ORDERS`.
32+
- An optional `automatic` key can also be provided to specify if that reducer should be loaded automatically, without having to dispatch the action. It defaults to `true`.
33+
34+
For the load and save method, you don't have to write them by yourself, they are [available as separate packages below](#providers).
2135

2236
```js
2337
// e.g. save and load methods -> available as separate packages below
2438
const saveState = (key, state) => ...
2539
const loadState = (key) => ...
2640

27-
// store.js
28-
29-
const middleware = [
30-
() => persistMiddleware(saveState, loadState)
31-
];
32-
33-
export const store = createStore(
34-
reducers,
35-
applyMiddleware(...middleware)
36-
);
37-
```
38-
39-
The next step is to define the structure of your store and use arrays to define what values you want the middleware to persist.
40-
41-
```js
42-
const persistThisData = {
41+
/**
42+
You define the structure that you want to save
43+
in this object, and then pass it as an argument.
44+
Instructions on how to create this object are above.
45+
*/
46+
const structure = {
4347
orders: {
4448
values: ["data", "updatedAt", "index"],
4549
key: "@key-orders",
@@ -65,19 +69,26 @@ const persistThisData = {
6569
}
6670
};
6771

68-
// Run this to start persisting data!
69-
createPersistMachine(persistThisData, store, true);
72+
// store.js
7073

74+
const persistMiddleware = createPersistMiddleware(
75+
structure, saveState, loadState
76+
)
77+
const middleware = [persistMiddleware];
78+
79+
export const store = createStore(
80+
reducers,
81+
applyMiddleware(...middleware)
82+
);
7183
```
7284

73-
The `createPersistMachine` takes three arguments: the persist object defined above, the redux store, and an optional bool value to enable debugging (default : false).
85+
Your reducer data will automatically saved when the values are changed. You can load each reducer using its load action (to see all the load actions generated in your console set the fourth parameter of `createPersistMachine` to `true`).
7486

75-
- The key of each value in the persist object should match your store shape, a nested reducer would be defined as such: "data.device". If nothinof provided to the `values` field, all fields will be saved.
76-
- A `key` is required to be defined for each persisted reducer, this will be used as the async storage key, it keeps a static map of your data which is independent of its shape, which is useful as your application grows.
77-
- An optional key of `action` can be defined to explicitly declare which action type should trigger a load of that reducer, without this value each load type is generated automatically from the state shape. e.g. to load `"data.device"` fire `LOAD_DATA_DEVICE`, to load `"subscriptionOrders"` fire `LOAD_SUBSCRIPTION_ORDERS`.
78-
- An optional `automatic` key can also be provided to specify if that reducer should be loaded automatically, without having to dispatch the action. It defaults to `true`.
87+
After setting the middleware in the store, you need to call `createPersistMachine.run` and pass the store as an argument.
7988

80-
Your reducer data will automatically saved when the values are changed. You can load each reducer using its load action (to see all the load actions generated in your console set the third parameter of `createPersistMachine` to `true`).
89+
```js
90+
createPersistMachine.run(store)
91+
```
8192

8293
### Loading Data
8394

@@ -94,7 +105,7 @@ case LOAD_SUBSCRIPTION_ORDERS: {
94105

95106
This allows you to have loading on a per reducer basis separated across the application for stored data instead of having the full application wait for the data to be loaded.
96107

97-
The middleware runs: `action.payload = { ...storedData, ...action.payload };` to add the saved data to the payload when the LOAD_ACTION is triggered. You can also pass additional data in your payload to add context to your `LOAD_ACTIONS` for complex conditional consummation of the loaded data in your reducers.
108+
The middleware runs: `action.payload = { ...storedData, ...action.payload };` to add the saved data to the payload when the `LOAD_ACTION` is triggered. You can also pass additional data in your payload to add context to your `LOAD_ACTIONS` for complex conditional consummation of the loaded data in your reducers.
98109

99110
## Providers
100111

lib/index.d.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ export declare type LoadCallback = (key: string) => Promise<object>;
1212
*
1313
* @return {function(*): function(*=): *}
1414
*/
15-
export declare const persistMiddleware: (save: SaveCallback, load: LoadCallback) => (next: any) => (action: any) => Promise<any>;
15+
declare function persistMiddleware(): (next: any) => (action: any) => Promise<any>;
16+
declare namespace persistMiddleware {
17+
let run: (store: any) => void;
18+
}
1619
/**
1720
* Persist Tree - Method to persist state data
1821
*
@@ -22,4 +25,5 @@ export declare const persistMiddleware: (save: SaveCallback, load: LoadCallback)
2225
* @param debug - Debug data to the console
2326
*
2427
*/
25-
export declare function createPersistMachine(structure: any, store: any, debug: boolean): void;
28+
export declare function createPersistMachine(structure: any, save: SaveCallback, load: LoadCallback, debug: boolean): typeof persistMiddleware;
29+
export {};

lib/index.js

Lines changed: 99 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -43,37 +43,36 @@ let loadMethod;
4343
*
4444
* @return {function(*): function(*=): *}
4545
*/
46-
export const persistMiddleware = (save, load) => (next) => (action) => __awaiter(void 0, void 0, void 0, function* () {
47-
// Assign our save and load methods
48-
saveMethod = save;
49-
loadMethod = load;
50-
// Gets the our trigger actions
51-
const currentValueActionAndKeys = Object.entries(currentValue)
52-
.map((item) => {
53-
return {
54-
action: item[1].action, key: item[1].key
55-
};
56-
});
57-
const targetActionAndKey = currentValueActionAndKeys
58-
.filter(item => (item.action === action.type))[0];
59-
// Only run this code for our defined load actions
60-
if (!_isNil(targetActionAndKey)) {
61-
const { key: asyncStorageKey } = targetActionAndKey;
62-
// If target is nil, then no need to attempt to load from async storage
63-
if (!_isNil(asyncStorageKey)) {
64-
// Invoke our load function on the target key
65-
let payload = yield loadMethod(asyncStorageKey);
66-
// Merge the payload received from our load function
67-
action.payload = Object.assign(Object.assign({}, payload), action.payload);
68-
// Update our current value target to isLoaded = true
69-
currentValue[asyncStorageKey] = Object.assign(Object.assign({}, _get(currentValue, asyncStorageKey, {})), { isLoaded: true });
70-
}
71-
else {
72-
action.payload = Object.assign({}, action.payload);
46+
function persistMiddleware() {
47+
return (next) => (action) => __awaiter(this, void 0, void 0, function* () {
48+
// Gets the our trigger actions
49+
const currentValueActionAndKeys = Object.entries(currentValue)
50+
.map((item) => {
51+
return {
52+
action: item[1].action, key: item[1].key
53+
};
54+
});
55+
const targetActionAndKey = currentValueActionAndKeys
56+
.filter(item => (item.action === action.type))[0];
57+
// Only run this code for our defined load actions
58+
if (!_isNil(targetActionAndKey)) {
59+
const { key: asyncStorageKey } = targetActionAndKey;
60+
// If target is nil, then no need to attempt to load from async storage
61+
if (!_isNil(asyncStorageKey)) {
62+
// Invoke our load function on the target key
63+
let payload = yield loadMethod(asyncStorageKey);
64+
// Merge the payload received from our load function
65+
action.payload = Object.assign(Object.assign({}, payload), action.payload);
66+
// Update our current value target to isLoaded = true
67+
currentValue[asyncStorageKey] = Object.assign(Object.assign({}, _get(currentValue, asyncStorageKey, {})), { isLoaded: true });
68+
}
69+
else {
70+
action.payload = Object.assign({}, action.payload);
71+
}
7372
}
74-
}
75-
return next(action);
76-
});
73+
return next(action);
74+
});
75+
}
7776
/**
7877
* Persist Tree - Method to persist state data
7978
*
@@ -83,7 +82,75 @@ export const persistMiddleware = (save, load) => (next) => (action) => __awaiter
8382
* @param debug - Debug data to the console
8483
*
8584
*/
86-
export function createPersistMachine(structure, store, debug) {
85+
export function createPersistMachine(structure, save, load, debug) {
86+
// Assign our save and load methods
87+
saveMethod = save;
88+
loadMethod = load;
89+
persistMiddleware.run = (store) => {
90+
/**
91+
* Handles state changes
92+
*
93+
* Saves the state values defined in structure when thats
94+
* slice of the state is updated.
95+
*/
96+
function handleChange() {
97+
return __awaiter(this, void 0, void 0, function* () {
98+
/*
99+
* We map the structure to support multiple
100+
* reducers. The `object` contains the keys that are
101+
* declared to be saved. The `name` is the key for
102+
* that reducer.
103+
*/
104+
yield _map(structure, (object, name) => __awaiter(this, void 0, void 0, function* () {
105+
// A key to keep the state mapping static
106+
const { key: asyncStorageKey } = object;
107+
// Get the state values we want to map.
108+
const stateValues = object.values;
109+
// Get the object we want from the currentValue
110+
const currentValueObject = currentValue[asyncStorageKey];
111+
// Gets the current state
112+
const state = select(store.getState(), name);
113+
// Gets the previous state
114+
const previousValue = _get(currentValueObject, "state", null);
115+
/*
116+
* Builds a state only containing the values
117+
* we care about, which are defined in structure.
118+
*/
119+
const newState = _pickBy(state, (value, key) => {
120+
/**
121+
* If nothing is passed to the `values`
122+
* parameter, all values will be used.
123+
*/
124+
if (_isNil(stateValues))
125+
return value;
126+
if (stateValues.includes(key))
127+
return value;
128+
});
129+
// Merges our newState object into our currentValues by key
130+
currentValue[asyncStorageKey] = Object.assign(Object.assign({}, _get(currentValue, asyncStorageKey, {})), { key: asyncStorageKey, state: newState });
131+
if (!_isEqual(previousValue, newState)) {
132+
/*
133+
* Each reducers value will have an `isLoaded` property, this
134+
* allows us to keep track on a individual level
135+
* if the reducer has been loaded. We don't want to
136+
* save the reducer if it has not been loaded yet.
137+
*/
138+
if (currentValue[asyncStorageKey].isLoaded) {
139+
if (debug)
140+
console.log(`SAVED: ${asyncStorageKey}`, newState);
141+
yield saveMethod(asyncStorageKey, newState);
142+
}
143+
}
144+
}));
145+
});
146+
}
147+
/*
148+
* Note that the .subscribe function returns a unsubscribe method if we
149+
* ever need to unsubscribe from state updates.
150+
*/
151+
store.subscribe(handleChange);
152+
loadAutomaticReducers(store);
153+
};
87154
/*
88155
* We do an initial map of the structure
89156
*/
@@ -99,75 +166,13 @@ export function createPersistMachine(structure, store, debug) {
99166
currentValue[asyncStorageKey] = Object.assign(Object.assign({}, _get(currentValue, name, {})), { key: asyncStorageKey, action,
100167
automatic, isLoaded: false });
101168
});
102-
/**
103-
* Handles state changes
104-
*
105-
* Saves the state values defined in structure when thats
106-
* slice of the state is updated.
107-
*/
108-
function handleChange() {
109-
return __awaiter(this, void 0, void 0, function* () {
110-
/*
111-
* We map the structure to support multiple
112-
* reducers. The `object` contains the keys that are
113-
* declared to be saved. The `name` is the key for
114-
* that reducer.
115-
*/
116-
yield _map(structure, (object, name) => __awaiter(this, void 0, void 0, function* () {
117-
// A key to keep the state mapping static
118-
const { key: asyncStorageKey } = object;
119-
// Get the state values we want to map.
120-
const stateValues = object.values;
121-
// Get the object we want from the currentValue
122-
const currentValueObject = currentValue[asyncStorageKey];
123-
// Gets the current state
124-
const state = select(store.getState(), name);
125-
// Gets the previous state
126-
const previousValue = _get(currentValueObject, "state", null);
127-
/*
128-
* Builds a state only containing the values
129-
* we care about, which are defined in structure.
130-
*/
131-
const newState = _pickBy(state, (value, key) => {
132-
/**
133-
* If nothing is passed to the `values`
134-
* parameter, all values will be used.
135-
*/
136-
if (_isNil(stateValues))
137-
return value;
138-
if (stateValues.includes(key))
139-
return value;
140-
});
141-
// Merges our newState object into our currentValues by key
142-
currentValue[asyncStorageKey] = Object.assign(Object.assign({}, _get(currentValue, asyncStorageKey, {})), { key: asyncStorageKey, state: newState });
143-
if (!_isEqual(previousValue, newState)) {
144-
/*
145-
* Each reducers value will have an `isLoaded` property, this
146-
* allows us to keep track on a individual level
147-
* if the reducer has been loaded. We don't want to
148-
* save the reducer if it has not been loaded yet.
149-
*/
150-
if (currentValue[asyncStorageKey].isLoaded) {
151-
if (debug)
152-
console.log(`SAVED: ${asyncStorageKey}`, newState);
153-
yield saveMethod(asyncStorageKey, newState);
154-
}
155-
}
156-
}));
157-
});
158-
}
159-
/*
160-
* Note that the .subscribe function returns a unsubscribe method if we
161-
* ever need to unsubscribe from state updates.
162-
*/
163-
store.subscribe(handleChange);
164-
loadAutomaticReducers(store);
165169
// If debug, we to log all the actions for loading the state
166170
if (debug) {
167171
_map(structure, (object, name) => __awaiter(this, void 0, void 0, function* () {
168172
console.log(object.action || buildAction(name));
169173
}));
170174
}
175+
return persistMiddleware;
171176
}
172177
/**
173178
* Dispatch actions to automatically load

0 commit comments

Comments
 (0)