feat: add working store implementation
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 8,
|
"ecmaVersion": 9,
|
||||||
"sourceType": "script"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"airbnb",
|
"airbnb",
|
||||||
|
|||||||
2
index.js
2
index.js
@@ -1,3 +1,3 @@
|
|||||||
const mod = require('./src/index.js');
|
const mod = require('./dist/index.js');
|
||||||
|
|
||||||
module.exports = mod;
|
module.exports = mod;
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
module.exports = function mod() {
|
|
||||||
// module code goes here
|
|
||||||
};
|
|
||||||
121
src/index.mjs
Normal file
121
src/index.mjs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/* eslint no-param-reassign: 0, react/destructuring-assignment: 0, react/no-multi-comp: 0 */
|
||||||
|
import React, { createContext, useContext } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { produce, original } from 'immer';
|
||||||
|
|
||||||
|
// keep track of all the stores
|
||||||
|
const stores = new Map();
|
||||||
|
|
||||||
|
// USAGE: createStore('myStore', { state, mutations, actions, getters })
|
||||||
|
export function createStore(name, storeSpec) {
|
||||||
|
// don't duplicate stores
|
||||||
|
if (stores.has(name)) throw new Error(`Store '${name}' already exists`);
|
||||||
|
|
||||||
|
const spec = {
|
||||||
|
state: {},
|
||||||
|
mutations: {},
|
||||||
|
actions: {},
|
||||||
|
getters: {},
|
||||||
|
...storeSpec,
|
||||||
|
};
|
||||||
|
|
||||||
|
// create a new context for the store and add it to the map
|
||||||
|
const storeContext = createContext({
|
||||||
|
state: spec.state,
|
||||||
|
commit: () => {},
|
||||||
|
dispatch: () => {},
|
||||||
|
getters: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
stores.set(name, {
|
||||||
|
context: storeContext,
|
||||||
|
provider: class StoreProvider extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
state: spec.state,
|
||||||
|
commit: (n, ...args) => {
|
||||||
|
const mutation = spec.mutations[n];
|
||||||
|
if (!mutation) throw new Error(`No such mutation: ${n}`);
|
||||||
|
this.setState(
|
||||||
|
produce(draft => {
|
||||||
|
mutation({ state: draft.state, original }, ...args);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
dispatch: async (n, ...args) => {
|
||||||
|
const action = spec.actions[n];
|
||||||
|
if (!action) throw new Error(`No such action: ${n}`);
|
||||||
|
return produce(this.state.state, async draft => {
|
||||||
|
// NOTE: this does not change state, it's expected that
|
||||||
|
// actions use commit to update the state
|
||||||
|
return action({ state: draft, commit: this.state.commit }, ...args);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get: (n, ...args) => {
|
||||||
|
const getter = spec.getters[n];
|
||||||
|
if (!getter) throw new Error(`No such getter: ${n}`);
|
||||||
|
return getter(this.state.state, ...args);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return React.createElement(
|
||||||
|
storeContext.Provider,
|
||||||
|
{ value: this.state },
|
||||||
|
this.props.children
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// TODO: keep track of the intial state, for resetting
|
||||||
|
// initialState: { ...spec.state },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: method to build a larger state object made of stores
|
||||||
|
// composeStores({
|
||||||
|
// todos: todoStore
|
||||||
|
// });
|
||||||
|
|
||||||
|
// hook to access a specific store and read/write methods
|
||||||
|
// USAGE: const { state, commit } = useStore('myStore')
|
||||||
|
export function useStore(name) {
|
||||||
|
if (!stores.has(name)) throw new Error(`Store does not exist: '${name}'`);
|
||||||
|
const store = stores.get(name);
|
||||||
|
return useContext(store.context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// provider to wrap your app or component
|
||||||
|
export class StateProvider extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
names: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
names: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { names, children } = this.props;
|
||||||
|
const namesArray = names.length ? names.split(',') : [];
|
||||||
|
|
||||||
|
// provide all stores if none are specified
|
||||||
|
const provideAllStores = namesArray.length === 0;
|
||||||
|
|
||||||
|
// build an array of all the store providers
|
||||||
|
const providers = provideAllStores
|
||||||
|
? Array.from(stores).map(([, store]) => store.provider)
|
||||||
|
: namesArray.map(name => {
|
||||||
|
if (!stores.has(name)) throw new Error(`Store does not exist: '${name}'`);
|
||||||
|
return stores.get(name).provider;
|
||||||
|
});
|
||||||
|
|
||||||
|
// nest children inside all of the providers
|
||||||
|
return providers.reduce((acc, provider) => {
|
||||||
|
return React.createElement(provider, {}, acc);
|
||||||
|
}, children);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user