React context hook: a 2kb library to manage state with Context and hooks, without redux
React Global State with Context and Hooks
A solution to manage the global state of a React application using hooks and Context API.
Great! You have learned React Hooks and you started using them, and now you are wondering which is the best way to share the state between Components: Redux, Saga, Thunk, Unstate, Mobx, Apollo, Relay?
Honestly, I think all these libs are great, but they all have one thing in common: they are not so easy and fast to learn and to use with Hooks.
I recently came up with a solution that made me save a lot of time programming and teaching to others how to share the global state of React Apps, and I want to share it with you. It’s not a fancy or complicated solution, it is simple and allows you to start coding in just 5 or 6 minutes reading.
The solution is a combination of these three components:
- A global Store, that acts as a JavaScript Map: an object with methods to set, get and delete data in it.
- A React Context Provider whose value is the Store, a.k.a. the Map.
- Some custom React Hooks to access, read, modify and delete values from the Store.
You don’t have to write or deeply understand these 3, I’ll show you the code and you’ll just need to read it.
To make it as easy as possible, you’ll see first the completed solution. Then, if you want, you can see how it works at the bottom of the story. This is the final result:
1. Wrap your React App with the function “withStore”.
const initialState = { count: 10 }export default withStore(App, initialState)
The store is created and initialized by the function withStore
(which is a React High Order Component) with an initialValue
, that is an object. This object contains a property count
. This property is saved to the Store Map with the key count
.
In the example, the value of count is a number, but it could be whatever you want: a String, Number, Boolean, Object, Array, Function, bytes, etc... This means that you can put anything in the Store.
2. Use the hooks to access, modify and delete data in the store.
const [count, setCount, deleteCount] = useStore('count')
...<button onClick={() => setCount(count - 1)}>Decrement - </button><span className="count">{count}</span>
<button onClick={() => setCount(count + 1)}>Increment + </button>
<button onClick={() => deleteCount()}>Delete "count" from store</button>
The hook
useStore
is a function that accepts a key to lookup in the Store map. In the example, you are asking for the keycount
.The hook
useStore
is similar touseState
from React, and it returns an array with 3 entries:
- The first is the value of the data in the store
- The second is a function to modify the data
- The third is a function to delete the data from the store
Of course, you can use a default value like in useState
.
const [username] = useStore('login.username', 'default value')
Similar to useState
you can name the entries of the array as you want. For example:
const [myValue, modifyMyValue, deleteMyValue] = useStore('login.username')const [cookies, setCookies, removeCookies] = useStore('user.cookies')
And, if you don’t need the delete
or set
functions, you can just ignore it:
//uses only the value and the setValue function
const [username, setUsername] = useStore('user.username')//uses only the value
const [username] = useStore(’user.username’)
You may be thinking that if you need the value
and the delete
function, but not the set
function, you end up with ESLint warning, like this:
That’s why there should be other hooks, like: useGetAndDelete
(when you need only the value and the delete function) or useSetAndDelete
(when you don’t need to read the value). These hooks do exist and I’ll talk about them later on.
Before going on, you should have a look and play with this dummy and boring App of number increment on CodeSandbox playground which is a classic.
Now, I want to share a 1.5Kb library with all the stuff mentioned until now and more, packaged, well tested and ready to use. This is the solution I mentioned earlier, it works pretty well and I’m already using it on my personal and business projects.
There is also a demo of the project, and you can play with it here: React Context Hook Demo. If you spend 10 minutes trying to use the library you can test yourself if it fits your needs. Everybody is in a rush when working on a project, and not using the right tools could slow down the whole development process. I don’t want you to regret to have ignored a solution that actually works. So have a look at it, talk with your co-worker, and please let me know what you think about it.
It is an open-source project, the feedback you give will help other developers all around the world. And yes, if you do that, you are actively supporting the open-source community.
How it works
The Store
The function
withStore
is a High Order Component that provides the React Context. The source code looks like this:
function withStore (WrappedComponent, initialValue) {
return function(props) {
const store = createStore(initialValue)
return (
<StoreContext.Provider value={store}>
<WrappedComponent {...props} />
</StoreContext.Provider>
)
}
}
It takes a React Component as an argument, and wrap it with a React Context Provider. The value of the context is the Store. The Store is an object that acts as a Javascript Map, and its code looks like this:
function createStore(initialValue) {
const [state, setState] = useState({ ...initialValue })
return {
get: (key, defaultValue) => {
let value = state[key]
if (value === undefined) {
value = defaultValue
}
return value
},
set: (key, value) => {
state[key] = value
setState(state)
},
remove: key => {
delete state[key]
setState(state)
}
}
}
The function
createStore
returns an object that hasget
,set
andremove
methods. These methods use the React HookuseState
, so when called, the store (which is the value of the Context) changes, the context is updated, so the React Components who consumes it using theuseStore
Hook gets re-rendered with the new values.
The Hooks
This is the code of the Hook useStore
:
import { useContext } from 'react'function useStore(key, defaultValue) {
const store = useContext(StoreContext)
const value = store.get(key, defaultValue)
const setValue = newValue => store.set(key, newValue)
const deleteValue = () => store.remove(key)
return [value, setValue, deleteValue]
}
The function
useStore
consumes the Store using the React HookuseContext
which returns the value of the React Context (the Store).
The function returns an array with 3 entries:
- The first is the value taken from the store
- The second is a function
setValue
that updates the Store - The third is a function
deleteValue
that removes the value from the Store
Other Hooks
The library react-context-hook
provides some other hooks:
- useStoreState: Returns the whole store value, with all the variables stored in it. Changes applied to this object will not change the store.
import { useStoreState } from 'react-context-hook'
const store = useStoreState()
console.log('the store is', JSON.stringify(store))
- useSetStoreValue: Returns a function to set or update a variable in the store. You want to use this hook when you just need to modify the store, not read or delete values from it.
import { useSetStoreValue } from 'react-context-hook'
const setUsername = useSetStoreValue('username')
<button onClick={()=> setUsername('my_username')}>set username</button>
- useDeleteStoreValue: Returns a function to delete a variable in the store. You want to use this hook when you just need to delete a value in the store, not read or set values from it.
import {useDeleteStoreValue} from 'react-context-hook'
const deleteUsername = useDeleteStoreValue('username')
<button onClick={()=> deleteUsername('my_username')}>set username</button>
- useGetAndset: This React hook returns an array to read and modify a value in the store:
const [value, setValue] = useGetAndset('a_lookup_key_in_the_store')
. The name of the variable in the array is arbitrary and you can choose any string you like.
import {useGetAndset} from 'react-context-hook'
const [username, setUsername] = useGetAndset('username')
<div>hello {username}</div>
<button onClick={()=> setUsername('my_username')}>set username</button>
const [value, setValue] = useGetAndset('a_lookup_key_in_the_store')
- useGetAndDelete: This React hook returns an array to read and delete a value in the store:
const [value, deleteValue] = useGetAndDelete('a_lookup_key_in_the_store')
. The name of the variable in the array is arbitrary and you can choose any string you like.
import {useGetAndDelete} from 'react-context-hook'
const [username, deleteUsername] = useGetAndDelete('username')
<div>hello {username}</div>
<button onClick={()=> deleteUsername('my_username')}>delete username</button>
- useSetAndDelete: This React hook returns an array to set and delete a value in the store:
const [setValue, deleteValue] = useGetAndDelete('a_lookup_key_in_the_store')
. The name of the variable in the array is arbitrary and you can choose any string you like.
import {useGetAndDelete} from 'react-context-hook'
const [username, deleteUsername] = useGetAndDelete('username')
<div>hello {username}</div>
<button onClick={()=> deleteUsername('my_username')}>set username</button>
- useStoreValue: Returns any the value on the global store, or the default value if passed, or
undefined
That’s pretty much it, hope you’ll have fun with it.
Congratulations!! 😎 You made it to the end. And if you liked 👌 this article, hit that clap button below 👏. It means a lot to me and it helps other people see the story.
More stories by Lorenzo Spyna