Tutorial

Easy State Management in React Using Unstated

Published on April 19, 2018
Default avatar

By Alligator.io

Easy State Management in React Using Unstated

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

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!

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Alligator.io

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel