Skip to content

Instantly share code, notes, and snippets.

@eliashussary
Last active June 27, 2018 20:21
Show Gist options
  • Save eliashussary/8300a41e83706a1ccea8e2a31e882212 to your computer and use it in GitHub Desktop.
Save eliashussary/8300a41e83706a1ccea8e2a31e882212 to your computer and use it in GitHub Desktop.
A proof of concept for a simple React state management tool using the Context API.
/**
* Title: Simple React State Management with Context
* Description:
* This is a React State Management proof of concept which uses the React ^16 Context API.
* This POC is loosely based on redux, with the ommission of reducers to limit the amount of boilerplate required.
*
* ---
* Author: Elias Hussary <[email protected]>
* Created: 2018-06-07
* Updated: 2018-06-07
*/
import React from "react";
// Create your react context and extract Provider / Consumer
const { Provider, Consumer } = React.createContext();
// React component responsible for all the state management of your application.
class StoreProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
store: props.store || {},
actions: this.__bindActions(props.actions),
// expose internals as private methods
__getState: this.__getState,
__apply: this.__apply
};
}
// wraps an action and provides the context of this component
__bindAction = action => {
return (...args) => {
const func = action.apply(null, args);
func.apply(this, [this.__apply, this.__getState]);
};
};
// iterate through the actions object and wraps each action
__bindActions = actions => {
return Object.keys(actions).reduce((obj, action) => {
const func = actions[action];
obj[action] = this.__bindAction(func);
return obj;
}, {});
};
// updates your state
__apply = data => {
this.setState({
store: {
...this.state.store,
...data
}
});
};
// gets state of your application
__getState = key => {
if (key) {
return this.state.store[key];
} else {
return this.state.store;
}
};
// renders the provider
render() {
return <Provider value={this.state}>{this.props.children}</Provider>;
}
}
/**
* A HOC which initializes the Context StoreProvider for your application.
*
* @param {Object} initialState - The initial state of your application.
* @param {Object} actions - The actions which mutate the state of your application.
* @return {Component} The HOC Context Provider
* @example
* import React from "react";
* import { render } from "react-dom";
*
* import { CreateStore, Consumer } from "...";
*
* const initialState = { value: 0 };
*
* const actions = {
* increment: (amount = 1) => (apply, getState) => {
* const { value } = getState();
* apply({ value: value + amount });
* }
* };
*
* const LocalStore = CreateStore(initialState, actions);
*
* const App = () => {
* return (
* <LocalStore>
* <Consumer>
* {({ store, actions }) => (
* <div>
* <pre>{store.value}</pre>
* <button onClick={() => actions.increment()}>Increment By 1</button>
* <button onClick={() => actions.increment(2)}>Increment By 2</button>
* </div>
* )}
* </Consumer>
* </LocalStore>
* );
* };
*
* render(<App />, document.getElementById("root"));*
*/
function CreateStore(initialState, actions) {
return function Store(props) {
return (
<StoreProvider store={initialState} actions={actions}>
{props.children}
</StoreProvider>
);
};
}
function pick(obj = {}, keys = []) {
if (!keys.length) {
return obj;
}
const returnObj = {};
for (let key of keys) {
if (key in obj) {
returnObj[key] = obj[key];
}
}
return returnObj;
}
function ConnectComponent(stateKeys, actionKeys, Component) {
return function ConnectedComponent(props) {
return (
<Consumer>
{({ store, actions }) => {
store = pick(store, stateKeys);
actions = pick(actions, actionKeys);
return (
<Component store={store} actions={actions}>
{props.children}
</Component>
);
}}
</Consumer>
);
};
}
export { CreateStore, Consumer, ConnectComponent };
export default { CreateStore, Consumer, ConnectComponent };
import React from "react";
import { render } from "react-dom";
import { CreateStore, Consumer, ConnectComponent } from "./Store";
const initialState = { value: 0, value1: 1 };
const actions = {
increment: (amount = 1) => (assign, getState) => {
const { value } = getState();
assign({ value: value + amount });
},
increment1: (amount = 1) => (assign, getState) => {
const { value1 } = getState();
assign({ value1: value1 + amount });
}
};
const LocalStore = CreateStore(initialState, actions);
const ConnectedComponent = ConnectComponent(
["value"],
["increment1"],
props => {
return (
<div>
<hr />
Connected Component
<pre>{JSON.stringify(props.store, null, "\t")}</pre>
<button onClick={() => props.actions.increment1()}> Increment 1</button>
</div>
);
}
);
const App = () => {
return (
<LocalStore>
<Consumer>
{({ store, actions }) => (
<div>
Inline Component
<pre>{JSON.stringify(store, null, "\t")}</pre>
<button onClick={() => actions.increment()}>Increment By 1</button>
<button onClick={() => actions.increment(2)}>Increment By 2</button>
</div>
)}
</Consumer>
<ConnectedComponent />
</LocalStore>
);
};
render(<App />, document.getElementById("root"));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment