URL: https://www.progressiverobot.com/react-unstated/

Another day, another way to manage your application state in React! Unstated is a new library by @jamiebuilds that uses the React's new context API to allow for a really simple way to manage your state.

Let's go over how to use Unstated, and don't worry, it'll be totally painless!

Installation

state illustration for: Installation

Just install the <^>unstated<^> package using npm or Yarn:

				
					
$ npm install unstated



# or

$ yarn add unstated

				
			

Usage

Unstated gives us 3 things: a <^>container<^> class, a <^>Subscribe<^> component and a <^>Provider<^> component:

Container

You create a container for a piece of your state by extending Unstated's <^>Container<^> class:

				
					
[label ./containers/todo.js]

import { Container } from 'unstated';



class TodoContainer extends Container {

  // ...

}



export default TodoContainer;

				
			

And in your container you manage the state using <^>state<^> and <^>setState<^>, as you're already used to doing for local component state in React:

				
					
[label ./containers/todo.js]

import { Container } from 'unstated';



class TodoContainer extends Container {

  state = {

    todos: []

  };



  id = 0;



  addTodo = todo =&gt; {

    const newTodo = { id: this.id++, marked: false, description: todo };

    this.setState({

      todos: [...this.state.todos, newTodo]

    });

  };



  removeTodo = id =&gt; {

    this.setState({

      todos: this.state.todos.filter(todo =&gt; todo.id !== id)

    });

  };



  markTodo = id =&gt; {

    this.setState({

      todos: this.state.todos.map(todo =&gt; {

        if (todo.id !== id) {

          return todo;

        } else {

          return { ...todo, marked: !todo.marked };

        }

      })

    });

  };

}



export default TodoContainer;

				
			

As you can see, all the business logic for our todos is contained within the container. Our React components will be able to subscribe to the state from this container and to change the state by calling the methods defined in the container. And then changed state in a container with trigger a re-render of the subscriber components.

Provider

The <^>Provider<^> component is used to store the container instances and will allow its children to subscribe to the instances. Just use it at a top level around components that will subscribe to a container:

				
					
[label App.js]

import React, { Component } from 'react';

import { Provider } from 'unstated';



import Todos from './Todos';

import AddTodo from './AddTodo';



class App extends Component {

  render() {

    return (

      &lt;Provider&gt;

        &lt;AddTodo /&gt;

        &lt;Todos /&gt;

      &lt;/Provider&gt;

    );

  }

}



export default App;

				
			

Subscribe

And then finally, Unstated's <^>Subscribe<^> component takes a <^>to<^> prop with an array of containers and expects a function as its <^>children<^> prop (see render prop) that will receive an instance of each container it subscribes to.

With our example, our <^>AddTodo<^> component can look like this:

				
					
[label AddTodo.js]

import React from 'react';

import { Subscribe } from 'unstated';



import TodoContainer from './containers/todo';



class AddTodo extends React.Component {

  inputRef = React.createRef();



  handleClick = addTodo =&gt; {

    if (this.inputRef.current.value) {

      addTodo(this.inputRef.current.value);

      this.inputRef.current.value = '';

    }

  };



  render() {

    return (

      &lt;div&gt;

        &lt;input type="text" placeholder="your new todo" ref={this.inputRef} /&gt;



        &lt;Subscribe to={[TodoContainer]}&gt;

          {todoContainer =&gt; (

            &lt;button onClick={() =&gt; this.handleClick(todoContainer.addTodo)}&gt;

              Add

            &lt;/button&gt;

          )}

        &lt;/Subscribe&gt;

      &lt;/div&gt;

    );

  }

}



export default AddTodo;

				
			

Notice how get access to a todo container instance and can call its addTodo method. Here we're also making use of React's new createRef API.

Let's also use Unstated's <^>Subscribe<^> component in our <^>Todos<^> component to subscribe to our todo container, display the todos contained in its state and allow to mark them as completed or remove them:

				
					
[label Todos.js]

import React from 'react';

import { Subscribe } from 'unstated';



import TodoContainer from './containers/todo';



class Todos extends React.Component {

  render() {

    return (

      &lt;ul&gt;

        &lt;Subscribe to={[TodoContainer]}&gt;

          {todoContainer =&gt;

            todoContainer.state.todos.map(todo =&gt; (

              &lt;li key={todo.id}&gt;

                &lt;span

                  className={todo.marked ? 'marked' : null}

                  onClick={() =&gt; todoContainer.markTodo(todo.id)}

                &gt;

                  {todo.description}

                &lt;/span&gt;

                &lt;button onClick={() =&gt; todoContainer.removeTodo(todo.id)}&gt;

                  X

                &lt;/button&gt;

              &lt;/li&gt;

            ))

          }

        &lt;/Subscribe&gt;

      &lt;/ul&gt;

    );

  }

}



export default Todos;

				
			

Multiple Containers

It's just as easy to separate different pieces of state into their own separate containers. Say, for example, that our app now needs to also handle tasks. Let's refactor our <^>AddTodo<^> component into a <^>AddItem<^> component that also subscribes to a <^>TaskContainer<^>.

We'll use a couple of radio buttons to let the user choose between adding a todo or a task, and we'll also change our text input element to be a controlled input instead of making use of a ref:

				
					
[label AddItem.js]

import React from 'react';

import { Subscribe } from 'unstated';



import TodoContainer from './containers/todo';

import TaskContainer from './containers/task';



class AddItem extends React.Component {

  state = {

    itemValue: '',

    itemChoice: 'todo'

  };



  handleInputChange = e =&gt; {

    this.setState({

      itemValue: e.target.value

    });

  };



  handleRadioChange = e =&gt; {

    this.setState({

      itemChoice: e.target.value

    });

  };



  handleClick = (addTodo, addTask) =&gt; {

    if (this.state.itemValue &amp;&amp; this.state.itemChoice === 'todo') {

      addTodo(this.state.itemValue);

    } else if (this.state.itemValue) {

      addTask(this.state.itemValue);

    }



    this.setState({

      itemValue: ''

    });

  };



  render() {

    return (

      &lt;div&gt;

        &lt;input

          type="text"

          placeholder="your new item"

          value={this.state.itemValue}

          onChange={this.handleInputChange}

        /&gt;



        &lt;input

          type="radio"

          id="todoItem"

          name="itemType"

          value="todo"

          checked={this.state.itemChoice === 'todo'}

          onChange={this.handleRadioChange}

        /&gt;

        &lt;label htmlFor="todoItem"&gt;Todo&lt;/label&gt;



        &lt;input

          type="radio"

          id="taskItem"

          name="itemType"

          value="task"

          checked={this.state.itemChoice === 'task'}

          onChange={this.handleRadioChange}

        /&gt;

        &lt;label htmlFor="taskItem"&gt;Task&lt;/label&gt;



        &lt;Subscribe to={[TodoContainer, TaskContainer]}&gt;

          {(todoContainer, taskContainer) =&gt; (

            &lt;button

              onClick={() =&gt;

                this.handleClick(todoContainer.addTodo, taskContainer.addTask)

              }

            &gt;

              Add {this.state.itemChoice}

            &lt;/button&gt;

          )}

        &lt;/Subscribe&gt;

      &lt;/div&gt;

    );

  }

}



export default AddItem;

				
			

Now we can separate the display our todos and tasks inside our component tree, with the <^>Todos<^> component subscribing to the <^>TodoContainer<^> and the <^>Tasks<^> component subscribing to the <^>TaskContainer<^>. Here's what our <^>App<^> component can look like, for example:

				
					
[label App.js]

// ...



class App extends Component {

  render() {

    return (

      &lt;Provider&gt;

        &lt;AddItem /&gt;

        &lt;Todos /&gt;

        &lt;Tasks /&gt;

      &lt;/Provider&gt;

    );

  }

}



export default App;

				
			

🥧 Easy as pie! Now you have one more tool in your arsenal for when it comes to organizing and managing your React app state. The tool you reach for is up to you, but I'd say that Unstated is a pretty darn good option for a lot of use cases!