Last active
June 27, 2018 20:21
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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