Outstated - simple hooks-based state management for React
Tags: react, javascript, hooks
Published: 2019-02-13 16:44:02

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](https://github.com/jamiebuilds/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](https://github.com/yamalight/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

```jsx
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](https://github.com/yamalight/outstated/blob/master/example/withhooks.js) within them).

Here's an example of a very simple store:

```js
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.:

```jsx
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.

```jsx
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`:

```js
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](https://github.com/yamalight/outstated/).
Any feedback is appreciated :)