If you ever built React apps, you know that state management can be a pain.
Redux is great, but often is too much for small (and even some medium-sized) projects.
Unstated has simplified state management for smaller project using context and custom classes. It is quite awesome, but doesn't really work with new shiny React hooks.
So, I was wondering whether I could build something similar to Unstated but with all the hooks goodness to make it even nicer.
Introducing Outstated
Outstated is a simple hook-based state management solution for React.
It takes normal React hooks and allows you to share them to have a global state.
Oh, have I mentioned that it's just 474 bytes minzipped?
Example
import React, {useState} from 'react';
import ReactDOM from 'react-dom';
import {Provider, useStore} from 'outstated';
const store = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(0);
return {count, increment, decrement, reset};
};
function Counter() {
const {count, increment, decrement, reset} = useStore(store);
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={reset}>reset</button>
</div>
);
}
ReactDOM.render(
<Provider stores={[store]}>
<Counter />
</Provider>,
document.getElementById('root')
);
How does it work?
Outstated is built on top of React hooks and context.
It is pretty much just a simple context wrapper around hooks that instantiates them once and allows accessing them where needed.
Outstated has three core parts:
Store
This is a place to store your state and the logic for updating it.
Stores are essentially React hooks (which means you can re-use them, use them outside of Outstated or even use other hooks within them).
Here's an example of a very simple store:
import {useState} from 'React';
const store = () => {
const [state, setState] = useState('hello..');
const greet = val => setState(`hello ${val}!`);
return {state, greet};
};
Note that it use useState
hook from React for managing state.
This means that Outstated doesn't have to manually manage anything related to state changes - React will handle this for us!
You do have to be careful not to mutate state
directly or your components won't re-render though.
useStore
Next piece is a useStore
hook.
It allows us to access the instantiated stores from anywhere in the tree.
useStore
hook allows us to get store instances by using specific store constructor, e.g.:
function Counter() {
const {count, decrement, increment} = useStore(counterStore);
return (
<div>
<span>{count}</span>
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
);
}
<Provider>
The final piece of Outstated is <Provider>
component.
It is responsible for initialization of global instances of given stores (this is required because React expects the number of hooks to be consistent across re-renders, so we cannot dynamically initialize them as needed).
It then uses context to pass initialized store instances to all the components down the tree.
render(
<Provider stores={[counterStore]}>
<Counter />
</Provider>
);
Testing
Because Outstated stores are just hooks, we can construct them in
tests and assert different things about them very easily.
Here's a basic example using awesome react-testing-library
:
import {act, testHook} from 'react-testing-library';
test('counter', async () => {
let count, increment, decrement;
testHook(() => ({count, increment, decrement} = counterStore()));
expect(count).toBe(0);
act(() => increment());
expect(count).toBe(1);
act(() => decrement());
expect(count).toBe(0);
});
Links
You can find Outstated on GitHub.
Any feedback is appreciated :)
Top comments (0)