Easy State Management in React Using 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

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:

./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:

./containers/todo.js

import { Container } from 'unstated';

class TodoContainer extends Container {
  state = {
    todos: []
  };

  id = 0;

  addTodo = todo => {
    const newTodo = { id: this.id++, marked: false, description: todo };
    this.setState({
      todos: [...this.state.todos, newTodo]
    });
  };

  removeTodo = id => {
    this.setState({
      todos: this.state.todos.filter(todo => todo.id !== id)
    });
  };

  markTodo = id => {
    this.setState({
      todos: this.state.todos.map(todo => {
        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:

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 (
      <Provider>
        <AddTodo />
        <Todos />
      </Provider>
    );
  }
}

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:

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 => {
    if (this.inputRef.current.value) {
      addTodo(this.inputRef.current.value);
      this.inputRef.current.value = '';
    }
  };

  render() {
    return (
      <div>
        <input type="text" placeholder="your new todo" ref={this.inputRef} />

        <Subscribe to={[TodoContainer]}>
          {todoContainer => (
            <button onClick={() => this.handleClick(todoContainer.addTodo)}>
              Add
            </button>
          )}
        </Subscribe>
      </div>
    );
  }
}

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:

Todos.js

import React from 'react';
import { Subscribe } from 'unstated';

import TodoContainer from './containers/todo';

class Todos extends React.Component {
  render() {
    return (
      <ul>
        <Subscribe to={[TodoContainer]}>
          {todoContainer =>
            todoContainer.state.todos.map(todo => (
              <li key={todo.id}>
                <span
                  className={todo.marked ? 'marked' : null}
                  onClick={() => todoContainer.markTodo(todo.id)}
                >
                  {todo.description}
                </span>
                <button onClick={() => todoContainer.removeTodo(todo.id)}>
                  X
                </button>
              </li>
            ))
          }
        </Subscribe>
      </ul>
    );
  }
}

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:

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 => {
    this.setState({
      itemValue: e.target.value
    });
  };

  handleRadioChange = e => {
    this.setState({
      itemChoice: e.target.value
    });
  };

  handleClick = (addTodo, addTask) => {
    if (this.state.itemValue && this.state.itemChoice === 'todo') {
      addTodo(this.state.itemValue);
    } else if (this.state.itemValue) {
      addTask(this.state.itemValue);
    }

    this.setState({
      itemValue: ''
    });
  };

  render() {
    return (
      <div>
        <input
          type="text"
          placeholder="your new item"
          value={this.state.itemValue}
          onChange={this.handleInputChange}
        />

        <input
          type="radio"
          id="todoItem"
          name="itemType"
          value="todo"
          checked={this.state.itemChoice === 'todo'}
          onChange={this.handleRadioChange}
        />
        <label htmlFor="todoItem">Todo</label>

        <input
          type="radio"
          id="taskItem"
          name="itemType"
          value="task"
          checked={this.state.itemChoice === 'task'}
          onChange={this.handleRadioChange}
        />
        <label htmlFor="taskItem">Task</label>

        <Subscribe to={[TodoContainer, TaskContainer]}>
          {(todoContainer, taskContainer) => (
            <button
              onClick={() =>
                this.handleClick(todoContainer.addTodo, taskContainer.addTask)
              }
            >
              Add {this.state.itemChoice}
            </button>
          )}
        </Subscribe>
      </div>
    );
  }
}

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:

App.js

// ...

class App extends Component {
  render() {
    return (
      <Provider>
        <AddItem />
        <Todos />
        <Tasks />
      </Provider>
    );
  }
}

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!

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#