README
React-Store-Hook
Simple hooks-based state management for React
Like unstated but with hooks
⚠️ Warning: React hooks are not part of a stable React release yet, so use this library only for experiments
Installation
npm install react-store-hook
Example
import React from 'react';
import ReactDOM from 'react-dom';
import {Container, useStore} from 'react-store-hook';
class CounterContainer extends Container {
state = {
count: 0,
};
increment = () => {
this.setState({count: this.state.count + 1});
};
decrement = () => {
this.setState({count: this.state.count - 1});
};
}
const counter = new CounterContainer();
function Counter() {
const [state, setState] = useStore(counter);
const reset = () => setState({count: 0});
return (
<div>
<button onClick={counter.decrement}>-</button>
<span>{state.count}</span>
<button onClick={counter.increment}>+</button>
<button onClick={reset}>reset</button>
</div>
);
}
ReactDOM.render(<Counter />, document.getElementById('root'));
For more examples, see the example/
directory.
Guide
Unstated is awesome, but doesn't really use hooks.
Can we build something similar to unstated with hooks to make something even nicer?
Introducing React-Store-Hook
I really like unstated. I really like hooks. I wanted a simple hook-based app state management solution. This is why I've built React-Store-Hook.
React-Store-Hook is built on top of React components, context and hooks and patterns surrounding those elements.
It has three pieces:
Container
It's a place to store our state and some of the logic for updating it.
Container
is a very simple class which is meant to look just like
React.Component
but with only the state-related bits: this.state
and
this.setState
.
class CounterContainer extends Container {
state = {count: 0};
increment = () => {
this.setState({count: this.state.count + 1});
};
decrement = () => {
this.setState({count: this.state.count - 1});
};
}
Note that Container
s are also event emitters that components subscribe to for updates.
When you call setState
it triggers components to re-render,
so be careful not to mutate this.state
directly or your components won't re-render.
setState()
setState()
in Container
mimics React's setState()
method as closely as
possible.
class CounterContainer extends Container {
state = {count: 0};
increment = () => {
this.setState(
state => {
return {count: state.count + 1};
},
() => {
console.log('Updated!');
}
);
};
}
useStore
Next we'll need a piece to introduce our state back into the tree so that:
- When state changes, our components re-render.
- We can depend on our container's state.
- We can call methods on our container.
For this we have the useStore
hook which allows us to pass our
container instances and receive references to state, update function and
the store itself (useful when passing the store using Provider
).
function Counter() {
const [state, setState, store] = useStore(counterStore);
return (
<div>
<span>{state.count}</span>
<button onClick={counterStore.decrement}>-</button>
<button onClick={counterStore.increment}>+</button>
</div>
);
}
useStore
will automatically construct our container and listen for changes.
<Provider>
The final optional piece that React-Store-Hook has is <Provider>
component.
It uses context to pass a given store instance to all the components down the tree.
render(
<Provider store={counterStore}>
<Counter />
</Provider>
);
Once you'd wrapped your components with <Provider>
you can simply use useStore
hook without any arguments:
function Counter() {
const [state, setState, store] = useStore();
return <div>{state.count}</div>;
}
Testing
Whenever we consider the way that we write the state in our apps we should be thinking about testing.
We want to make sure that our state containers have a clean way
Well because our containers are very simple classes, we can construct them in tests and assert different things about them very easily.
test('counter', async () => {
let counter = new CounterContainer();
assert(counter.state.count === 0);
counter.increment();
assert(counter.state.count === 1);
counter.decrement();
assert(counter.state.count === 0);
});