Skip to content
This repository has been archived by the owner on Sep 12, 2018. It is now read-only.

Commit

Permalink
always clear saved store when mounting; used persist in demo; updated…
Browse files Browse the repository at this point in the history
… docs;
  • Loading branch information
aprilmintacpineda committed Jun 24, 2018
1 parent 72a4aec commit 2cf8d70
Show file tree
Hide file tree
Showing 14 changed files with 293 additions and 36 deletions.
45 changes: 40 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@
This library is actively being maintained by the developer. Feature requests, enhancements, and bug reports are all welcome to the issue section.

# react-context-api-store
Seemless, lightweight, state management library that comes with asynchronous support out of the box. Inspired by Redux and Vuex. Built on top of [React's context api](https://reactjs.org/docs/context.html).
Seemless, lightweight, state management library that supports async actions and state persisting out of the box. Inspired by Redux and Vuex. Built on top of [React's context api](https://reactjs.org/docs/context.html).

# File size?
5kb transpiled, not minified.

# Use case
When you want a state management that's small and supports asynchronous actions out of the box.
6.7kb transpiled. Not minified. Not compressed. Not uglified.

# Example
https://aprilmintacpineda.github.io/react-context-api-store/#/
Expand Down Expand Up @@ -286,6 +283,44 @@ function myStateHandler (store, data) {
}
```

## Persisting states

If you want to persist states, just provide a second property called `persist` which is an object that has the following shape:

```js
{
storage: AsyncStorage, // the storage of where to save the state
statesToPersist: savedStore => {
// do whatever you need to do here
// then return the states that you want to save.
// NOTE: This is not strict, meaning, you can even
// create a new state here and it will still be saved
return {
someState: { ...savedStore.someState },
anotherState: [ ...savedStore.anotherState ],
someValue: savedStore.someValue
}
}
}
```

**example snippet**

```jsx
<Provider store={store} persist={{
storage: window.localStorage,
statesToPersist (savedStore) {
return { ...savedStore };
}
}}>
```

In this case I'm passing in the `window.localStorage` as the storage but you are free to use whatever storage you need but it must have the following methods:

- `getItem` which receives the `key` as the first parameter.
- `setItem` which receives the `key` as the first parameter and `value` as the second parameter.
- `removeItem` which receives the `key` as the first parameter.

# Related

- [inferno-context-api-store](https://github.com/aprilmintacpineda/inferno-context-api-store) inferno compatible version of the same thing.
12 changes: 8 additions & 4 deletions __tests__/connect.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ test('passes states as props to connected components', () => {
const persist = {
storage: {
getItem: jest.fn(),
setItem: jest.fn()
setItem: jest.fn(),
removeItem: jest.fn()
},
statesToPersist: () => ({})
};
Expand Down Expand Up @@ -46,7 +47,8 @@ test('calls persist.storage.setItem with default key when an action was dispatch
const persist = {
storage: {
getItem: jest.fn(),
setItem: jest.fn()
setItem: jest.fn(),
removeItem: jest.fn()
},
statesToPersist: () => ({})
};
Expand Down Expand Up @@ -117,7 +119,8 @@ test('calls persist.storage.setItem with custom key when an action was dispatche
const persist = {
storage: {
getItem: jest.fn(),
setItem: jest.fn()
setItem: jest.fn(),
removeItem: jest.fn()
},
statesToPersist: () => ({}),
key: 'my-custom-key'
Expand Down Expand Up @@ -189,7 +192,8 @@ test('does not call persist.storage.setItem action was dispatched an persist pro
const persist = {
storage: {
getItem: jest.fn(),
setItem: jest.fn()
setItem: jest.fn(),
removeItem: jest.fn()
},
statesToPersist: () => ({})
};
Expand Down
39 changes: 35 additions & 4 deletions __tests__/provider-persist.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ const persistedStates = {
};

const mockedStorage = {
getItem: () => JSON.stringify(persistedStates)
getItem: () => JSON.stringify(persistedStates),
setItem: jest.fn(),
removeItem: jest.fn()
};

test('matches snapshot when persist was provided', () => {
Expand All @@ -42,14 +44,27 @@ test('matches snapshot when persist was provided', () => {
).toMatchSnapshot();
});

test('calls persist.storage.getItem when persist prop was provided', () => {
test('calls persist.storage.getItem and persist.storage.removeItem when persist prop was provided', () => {
const persist = {
storage: {
getItem: jest.fn()
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn()
},
statesToPersist: jest.fn()
};

expect(renderer.create(
<Provider
store={store}
persist={persist}
>
<div>
<h1>This is the app</h1>
</div>
</Provider>
).getInstance().props.persist.storage.removeItem).toHaveBeenCalledWith('react-context-api-store');

expect(renderer.create(
<Provider
store={store}
Expand All @@ -74,12 +89,28 @@ test('calls persist.storage.getItem when persist prop was provided', () => {
</div>
</Provider>
).getInstance().props.persist.storage.getItem).toHaveBeenCalledWith('my-app-custom-key');

expect(renderer.create(
<Provider
store={store}
persist={{
...persist,
key: 'my-app-custom-key'
}}
>
<div>
<h1>This is the app</h1>
</div>
</Provider>
).getInstance().props.persist.storage.removeItem).toHaveBeenCalledWith('my-app-custom-key');
});

test('does not call persist.statesToPersist when store.getItem returned null', () => {
const persist = {
storage: {
getItem: () => null
getItem: () => null,
setItem: jest.fn(),
removeItem: jest.fn()
},
statesToPersist: jest.fn()
};
Expand Down
22 changes: 22 additions & 0 deletions example/build/app.js

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions example/build/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>React-Context-Store</title>
</head>
<body>
<div id="app"></div>
<script src="/app.js"></script>
</body>
</html>
6 changes: 3 additions & 3 deletions example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"webpack": "babel-node node_modules/webpack/bin/webpack.js --watch",
"build": "rm -rf build && npm run webpack",
"start": "npm run build && npm run serve",
"serve": "cp -R public/** build/ && serve -s build",
"webpack": "babel-node node_modules/webpack/bin/webpack.js",
"serve": "npm run webpack && cp -R public/** build/ && serve -s build",
"lint": "eslint src/**/*.js __tests__/**/*.js"
},
"repository": {
Expand Down
11 changes: 9 additions & 2 deletions example/src/entry.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { render } from 'react-dom';
import { HashRouter, Route, Link, Switch } from 'react-router-dom';
import Provider from 'react-context-api-store';
import Provider from './lib';

import routes from './routes';

Expand All @@ -10,7 +10,14 @@ import store from './store';
class App extends React.Component {
render () {
return (
<Provider store={store}>
<Provider store={store} persist={{
storage: localStorage,
statesToPersist (savedStore) {
return {
userState: { ...savedStore.userState }
};
}
}}>
<HashRouter>
<div>
<ul>
Expand Down
149 changes: 149 additions & 0 deletions example/src/lib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
'use strict';

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.connect = undefined;

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _propTypes = require('prop-types');

var _propTypes2 = _interopRequireDefault(_propTypes);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var StoreContext = _react2.default.createContext();

var connect = function connect(wantedState, wantedMutators) {
return function (WrappedComponent) {
return function (_React$Component) {
_inherits(Connect, _React$Component);

function Connect() {
var _ref;

var _temp, _this, _ret;

_classCallCheck(this, Connect);

for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}

return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Connect.__proto__ || Object.getPrototypeOf(Connect)).call.apply(_ref, [this].concat(args))), _this), _this.dispatcher = function (updateStore, storeState, action) {
return function () {
for (var _len2 = arguments.length, payload = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
payload[_key2] = arguments[_key2];
}

return action.apply(undefined, [{
state: _extends({}, storeState),
updateStore: updateStore
}].concat(payload));
};
}, _this.mapStateToProps = function (storeState) {
return wantedState ? wantedState(_extends({}, storeState)) : {};
}, _this.mapActionsToProps = function (updateState, storeState) {
return wantedMutators ? Object.keys(wantedMutators).reduce(function (accumulatedMutators, mutator) {
return _extends({}, accumulatedMutators, _defineProperty({}, mutator, _this.dispatcher(updateState, storeState, wantedMutators[mutator])));
}, {}) : {};
}, _this.render = function () {
return _react2.default.createElement(
StoreContext.Consumer,
null,
function (context) {
return _react2.default.createElement(WrappedComponent, _extends({}, _this.mapStateToProps(context.state), _this.mapActionsToProps(context.updateState, context.state)));
}
);
}, _temp), _possibleConstructorReturn(_this, _ret);
}

return Connect;
}(_react2.default.Component);
};
};

var Provider = function (_React$Component2) {
_inherits(Provider, _React$Component2);

function Provider() {
var _ref2;

var _temp2, _this2, _ret2;

_classCallCheck(this, Provider);

for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}

return _ret2 = (_temp2 = (_this2 = _possibleConstructorReturn(this, (_ref2 = Provider.__proto__ || Object.getPrototypeOf(Provider)).call.apply(_ref2, [this].concat(args))), _this2), _this2.state = _extends({}, _this2.props.store), _this2.persisted = false, _this2.updateState = function (updatedState) {
var newState = _extends({}, _this2.state, updatedState);

_this2.setState(newState);

if (_this2.props.persist !== false) {
_this2.props.persist.storage.removeItem(_this2.props.persist.key || 'react-context-api-store');
_this2.props.persist.storage.setItem(_this2.props.persist.key || 'react-context-api-store', JSON.stringify(newState));
}
}, _this2.render = function () {
return _react2.default.createElement(
StoreContext.Provider,
{ value: {
state: _extends({}, _this2.state),
updateState: _this2.updateState
} },
_this2.props.children
);
}, _temp2), _possibleConstructorReturn(_this2, _ret2);
}

_createClass(Provider, [{
key: 'componentDidMount',
value: function componentDidMount() {
if (this.props.persist !== false && !this.persisted) {
this.persisted = true;
var savedStore = this.props.persist.storage.getItem(this.props.persist.key || 'react-context-api-store');

this.updateState(savedStore ? this.props.persist.statesToPersist(JSON.parse(savedStore)) : {});
}
}
}]);

return Provider;
}(_react2.default.Component);

;

Provider.propTypes = {
children: _propTypes2.default.element.isRequired,
store: _propTypes2.default.object.isRequired,
persist: _propTypes2.default.oneOfType([_propTypes2.default.shape({
storage: _propTypes2.default.object.isRequired,
statesToPersist: _propTypes2.default.func.isRequired,
saveInitialState: _propTypes2.default.bool,
key: _propTypes2.default.string
}), _propTypes2.default.oneOf([false])])
};

Provider.defaultProps = {
persist: false
};

exports.connect = connect;
exports.default = Provider;
Loading

0 comments on commit 2cf8d70

Please sign in to comment.