From the docs:

Unstated is designed to build on top of the patterns already set out by React components and context.

What does this mean?

Well, we don’t need to learn a bunch of new APIs or libraries, we can just use state the way we would normally do in our regular React components, and everything just works.

Overview

Unstated builds upon three main ideas / components:

1. Container

Conceptually, you an think of a Container like a store.

Container is a very simple class which is meant to look just like React.Component but with only the state-related bits.

// BookContainer.js import { Container } from 'unstated' class BookContainer extends Container {

state = {

books: [],

booksVisible: false

}

addBook = book => {

const books = [...this.state.books, book]

this.setState({ books })

}

toggleVisibility = () => {

this.setState({

booksVisible: !this.state.booksVisible

})

}

} export {

BookContainer

}

2. Subscribe

Subscribe is how you pass the state from the Container to your Component. This will behave the same way that state behaves, i.e. when state changes, the components re-render, but you can also call methods on the Container .

import { Subscribe } from 'unstated'

import { BookContainer } from './BookContainer' <Subscribe to={[BookContainer]}>

{

bookStore => {

const { state: { books, booksVisible } } = bookStore

{

booksVisible && books.map((book, index) => (

<div>

<p>{book.name}</p>

<p>{book.author}</p>

</div>

)

}

}

}

</Subscribe>

3. Provider

The Provider is where we will store all of our instances internally. The unstated Provider works similarly to the Redux or Apollo Provider in the sense that is gracefully uses context to abstract away the API into something very consumable by our components.

import { Provider } from 'unstated'

import Main from './pathtomainentrypoint' const App = () => (

<Provider>

<Main />

</Provider>

)

Adding multiple Containers to a component

After looking at the API, one of the first questions I had was How do I add multiple stores to a component?

Well, the order in which you Subscribe to containers is the order in which they are available as child props.

For example say we had our existing BookStore but also a CounterStore like the one in the documentation. We could use both of them like this:

<Subscribe to={[BookContainer, CounterContainer]}>

{

(bookStore, counterStore) => {

// do stuff here

}

}

</Subscribe>

Lifecycle Methods

One question I had was “How do I access lifecycle methods if we are only accessing the container in render?, and “How do I work with asynchronous requests?”. Well, I reached out to James and he gave me a solid answer:

This is basically what I came up with:

export default class ComeClass extends Component {

render() {

return (

<Provider>

<Subscribe

to={[BookContainer, CounterContainer, LocationsContainer]}

>

{(bookStore, counterStore, locationsStore) => (

<SomeContainer

bookStore={bookStore}

CounterContainer={CounterContainer}

locationsStore={locationsStore}

/>

)}

</Subscribe>

</Provider>

);

}

}

Now, the SomeContainer component is where we not only render out UI, but also we can have access to lifecycle methods, which brings us to:

Asynchronous operations

With Redux, you may have used Thunk, Sagas, or even Redux Observable among other solutions to handle async operations. These tools are nice, but let’s be honest, they add complexity and overhead, and for beginners can be the difference between continuing on or giving up.

The beauty with this library is there are no plugins, middleware, or third party modules to handle async operations, you can just use fetch / axios / or your library of choice to handle xhr and everything just works.

This is coming from someone who loves and uses Redux in all of the projects I have control over.

Testing

Well testing seems pretty damn easy to me.

Here is an example from the documentation:

test('counter', () => {

let counter = new CounterContainer();

assert(counter.state.count === 0);



counter.increment();

assert(counter.state.count === 1);



counter.decrement();

assert(counter.state.count === 0);

});

Dependency injection

If we wanted to stub out a method in our state container, that is also pretty easy:

From the docs:

test('counter', () => {

let counter = new CounterContainer();

let inc = stub(counter, 'increment');

let dec = stub(counter, 'decrement');



let tree = render(

<Provider inject={[counter]}>

<Counter />

</Provider>

);



click(tree, '#increment');

assert(inc.calls.length === 1);

assert(dec.calls.length === 0);

});

Gripes

In the short time I’ve had to build with it, the only thing I wanted to do that I could not do is a callback style setState , but there seems to be a pull request for this exact thing so I think it will be added soon.

Conclusion

This tiny library is the evolution of state management, and to me makes so much more sense than any of the other current solutions (including Redux or MobX, coming from a fan of both Redux and MobX).

Why? Well we are reducing complexity and staying within the boundaries of React! If you understand how context works, you understand how this library works.

There are probably some best practices that need to be ironed out, and things that I will stumble upon that will trip me up, but I’m looking forward to using it in my next real project.

I am hesitant to say that it will replace any existing solution, and have yet to try it in production, but I am confident enough to use it in my next project and from there I guess we will see if there is anything I am missing in this analysis.

If you want to see a demo of this working in React Native along with examples of async, check out this repo.