Simplifying State Management in React with Signals
The Challenge of State Management
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.
Introducing Signia
Signia is a state management library that uses signals to solve these problems. It provides a new lazy reactivity model based on logical clocks, allowing for efficient calculation of computed values through 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.
const fruit = atom('fruit', 'Apple');
Updating an Atom
To update an Atom, use the set
function:
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.
const fruitCount = computed(() => {
return fruit.value.length;
});
Updating a computed signal is not possible directly. However, updating any of its root Atoms will automatically update the computed signal:
set(fruit, 'Orange');
console.log(fruitCount.value); // Output: 6
React Bindings for Signia
The official React bindings are shipped in two packages: signia-react
and signia-react-jsx
.
Getting Hands-on with Signia
Setting up Signia
Install the Signia-specific libraries:
npm install signia-react signia-react-jsx
Setting up Chakra UI
Install Chakra UI and its peer dependencies:
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:
import { useAtom } from 'ignia-react';
function Counter() {
const count = useAtom(0);
return (
<div>
Count: {count}
<br />
<button onClick={() => set(count, count + 1)}>Increment</button>
</div>
);
}
Designing the State
Design the state for our to-do list app:
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:
import { useTodoFromContext } from './TodoContext';
function TodoList() {
const todo = useTodoFromContext();
return (
<div>
{todo.title}
<ul>
{Object.values(todo.items).map((item) => (
<li key={item.id}>
<input type="checkbox" checked={item.completed} />
{item.text}
</li>
))}
</ul>
</div>
);
}
Sharing State between React Components
Use React Context to share state between different components:
// TodoContext.js
import { createContext, useContext } from 'eact';
import { atom } from 'ignia-react';
const TodoContext = createContext();
export function useTodoFromContext() {
return useContext(TodoContext);
}
// App.js
import { TodoContext } from './TodoContext';
function App() {
const todo = atom(new Todo());
return (
<TodoContext.Provider value={todo}>
<TodoList />
</TodoContext.Provider>
);
}