The first thing to do is to create a Context , Context.Provider and a Context.Consumer . The code is:

import React, { Component } from 'react' const StoreContext = React.createContext() class MyComponent extends Component {

static contextType = StoreContext

//this line does the magic, binding this.context to the value of the Provider render() {

return <div>Hello {this.context.name}</div>

}

} const MyApp = props => (

<div>

<MyComponent />

</div>

) class AppWithContext extends Component {

state = { name: 'Spyna' } render() {

return (

<StoreContext.Provider value={this.state}>

<MyApp />

</StoreContext.Provider>

)

}

} export default AppWithContext

The working code on CodeSandbox

In this code snippet, you created a React Context then you wrapped MyApp Component into the context Provider and finally, you have bound MyComponent with the context using static contextType = StoreContext .

Since we set the value property of StoreContext.Provider to this.state (that is the state of AppWithContext ), when something changes in that state, the AppWithContext will update, causing the re-render of its children.

To make these changes happen, you want some context (no pun intended) methods to: set , get , and remove data from the state. We used the AppWithContext state as the provider value, so we’ll to add these methods to it, that translated into code means:

class AppWithContext extends Component {

state = {

get: (key) => {

return this.state[key]

},

set: (key, value) => {

const state = this.state

state[key] = value

this.setState(state)

},

remove: key => {

const state = this.state

delete state[key]

this.setState(state)

}

} ... }

The provider value is this.state , so we added our methods there. You certainly noticed that the property name no longer exists in AppWithContext state , and we need to add it using the fresh, new and fabulous methods we’ve just created and added to the state. To test if this work, we’ll set the name property in MyComponent using the set method and we’ll read it in the render method using get .

class MyComponent extends Component {

static contextType = StoreContext

componentDidMount() {

let context = this.context

context.set('name', 'Spyna')

//sets the property 'name' with the value 'Spyna'

}



render() {

return <div>Hello {this.context.get('name')}</div>

//reads the property 'name' from the context

}

}

The working code on CodeSandbox

Yes, it works!

When the Component “did mount” we call context.set(’name’, ‘Spyna’) to set the property “name” in the context. Since the context is modified, the Component updates and when the render method is called, the value of “name” is printed. If you run this code you’ll see “Hello Spyna” printed on your screen.

This code doesn’t look pretty as I like. What we want to do is to make it a more declarative and easy to use. To achieve this goal we are going to create two High Order Component: one for the context Provider and the other one for the context Consumer .

According to React docs, a High Order Component is:

A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature. Concretely, a higher-order component is a function that takes a component and returns a new component. const EnhancedComponent = higherOrderComponent(WrappedComponent);

If you want to know more about HOC have a look here.

Store Provider HOC

const createStore = WrappedComponent => {

return class extends React.Component {

state = {

get: key => {

return this.state[key]

},

set: (key, value) => {

const state = this.state

state[key] = value

this.setState(state)

},

remove: key => {

const state = this.state

delete state[key]

this.setState(state)

}

}

render() {

return (

<StoreContext.Provider value={this.state}>

<WrappedComponent {...this.props} />

</StoreContext.Provider>

)

}

}

}

If this code looks strange for you, have a look at the version without arrow functions => : https://codesandbox.io/s/l21x87543z?autoresize=1&hidenavigation=1&module=%2Fsrc%2Fstore.js

The function createStore returns a Component that renders any Component passed as an argument inside the context Provider.

To use this HOC, all you have to do is just:

export default createStore(MyApp);

Store Consumer HOC

const withStore = WrappedComponent => {

return class extends React.Component {

render() {

return (

<StoreContext.Consumer>

{context => <WrappedComponent store={context} {...this.props} />}

<StoreContext.Consumer>

)

}

}

}

The function withStore returns a Component that renders any Component you passed as an argument inside the context Consumer. Plus it injects a prop called store into your Component, that is the context value.

Now you can access the context, doing:

const MyComponent = (props) => <div>{props.store.get('data')}</div> export default withStore(MyComponent)

Putting all together

Let’s create a file called store.js , add the two HOC and export them.

import React from 'react' const StoreContext = React.createContext() const createStore = WrappedComponent => {

return class extends React.Component {

state = {

get: key => {

return this.state[key]

},

set: (key, value) => {

const state = this.state

state[key] = value

this.setState(state)

},

remove: key => {

const state = this.state

delete state[key]

this.setState(state)

}

}

render() {

return (

<StoreContext.Provider value={this.state}>

<WrappedComponent {...this.props} />

</StoreContext.Provider>

)

}

}

} const withStore = WrappedComponent => {

return class extends React.Component {

render() {

return (

<StoreContext.Consumer>

{context => <WrappedComponent store={context} {...this.props} />}

</StoreContext.Consumer>

)

}

}

} export { createStore, withStore }

And finally, use the HOCs, with the below code, that looks much prettier than it looked in the beginning:

// MyComponent.js

import React, { Component } from 'react'

import { withStore} from './store'; class MyComponent extends Component {

componentDidMount() {

this.props.store.set('name', 'Spyna')

} render() {

return <div>Hello {this.props.store.name}</div>

}

} export default withStore(MyComponent) // App.js

import React, { Component } from 'react'

import { createStore} from './store'

import MyComponent from './MyComponent'

//import MyOtherWithStoreComponent from './MyOtherComponent' const MyApp = props => (

<div>

<MyComponent />

{/*<MyOtherWithStoreComponent />*/}

</div>

) export default createStore(MyApp)

Considerations

What you’ve done is just the beginning of a journey managing the state of your app. You may want to add some features to the store you already created. For example, you want to:

set an initial value , maybe reading it from the local storage or from an API: createStore(MyApp, initialValue) .

, maybe reading it from the local storage or from an API: . get a default value if there are no data in the store: store.get('my_key', defaultValue) .

if there are no data in the store: . provide immutability to the store methods, to avoid doing any damage by mistake, for example, if you call store.get = () => 'hey I just broke everything' you’ll break the get method.

to the store methods, to avoid doing any damage by mistake, for example, if you call you’ll break the method. isolate store methods from its values: now you can access a value from the store either doing store.get(’my_key’) or store.my_key . This is not bad at all, maybe you want to make this behavior configurable.

store methods from its values: now you can access a value from the store either doing or . This is not bad at all, maybe you want to make this behavior configurable. make the store methods returns a Promise so that you can use: store.remove(’my_key’).then(() =>{ doSomething() }) .

All these ideas are great and you may think it takes a lot of time to implement them, that’s why I created the project react-store that already does all those stuff for you. The codebase is very small and if you want to copy it, have a look at the repo linked below.

As I stated at the beginning I’m a fan of Redux and think it is very useful, mostly when you deal with big projects. However, I find it a bit overkilling and uncomfortable to set up in small projects.