Simplifying State Management in React with Signia
As React applications grow in complexity, state management can become a significant challenge. While native tools like useState
and useContext
provide some relief, they often fall short when implementing common design patterns, such as a central, shared state consumed by multiple components. This is where Signia, a state management library that uses signals to solve these problems, comes into play.
What is Signia?
Signia is an original library for working with fine-grained reactive values, called signals, using a new lazy reactivity model based on logical clocks. In simpler terms, Signia uses primitives called signals for state management, which can efficiently calculate computed values by performing incremental calculations. Additionally, internal clocks provide support for transaction rollbacks if required.
Understanding Signals
A signal is a value that changes over time, and its change events can trigger side effects. In other words, a signal is a pure, reactive value that can be observed for changes. It’s the responsibility of the signal’s library to observe these changes, notify subscribers, and trigger the required side effects.
Signia Core Concepts
Atom
An Atom in Signia represents the signals that correspond to the root state, i.e., the source of truth for your app. Its value can be read and updated, as well as built upon to create computed values.
Creating an Atom
To create an Atom, the Signia library provides the atom
function:
javascript
const fruit = atom('fruit', 'Apple');
Updating an Atom
To update an Atom, use the set
function:
javascript
set(fruit, 'Banana');
Computed Signals
Computed signals are derived from Atoms and have a dependency on them; their values are recomputed whenever the Atoms they depend on change.
Creating a Computed Signal
Create a computed signal using the computed
function:
javascript
const fruitCount = computed(() => {
return fruit.value.length;
});
Updating a Computed Signal
There is no direct way to update a computed signal. However, updating any of its root Atoms will automatically update the computed signal:
javascript
set(fruit, 'Orange');
console.log(fruitCount.value); // Output: 6
React Bindings for Signia
The code examples above are generic, using the Signia core library. However, the tldraw team has released a set of React bindings that make integrating Signia into a React application easier. The official React bindings are shipped in two packages: signia-react
and signia-react-jsx
.
Getting Hands-on with Signia
Let’s create a React to-do list app using Signia for state management.
Setting up Signia
Install the Signia-specific libraries:
bash
npm install signia-react signia-react-jsx
Setting up Chakra UI
Install Chakra UI and its peer dependencies:
bash
npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion
Testing Reactivity with Signia
Create a simple counter app that uses Signia for state management:
“`javascript
import { useAtom } from ‘signia-react’;
function Counter() {
const count = useAtom(0);
return (
Count: {count}
);
}
“`
Designing the State
Design the state for our to-do list app:
“`javascript
class Todo {
items = {};
title = ”;
addItem(id, text) {
this.items[id] = { id, text, completed: false };
}
markItemAsDone(id) {
this.items[id].completed = true;
}
setTitle(title) {
this.title = title;
}
}
“`
Creating the UI
Create the UI for our to-do list app:
“`javascript
import { useTodoFromContext } from ‘./TodoContext’;
function TodoList() {
const todo = useTodoFromContext();
return (
{todo.title}
-
{Object.values(todo.items).map((item) => (
{item.text}
))}
);
}
“`
Sharing State between React Components
Use React Context to share state between different components:
“`javascript