Easier state management with Preact Signals
Yesterday, we looked at a mostly vanilla JS way to use Preact for state-based UI.
Today, we’re going to look at an easier way to manage state with Signals, since having to reset the value
property can be clunky with arrays and objects.
Let’s dig in!
Creating a state store
For this approach, we’re going to create a store: an object of functions we can use to modify our state for us.
When we’re done, we’ll be able to run functions like todos.add()
and todos.remove()
instead of having to manually update todos.value
over and over again.
To start, we’ll create an IIFE and assign that to our todos
variable instead of the Signals object.
let todos = (function () {
// Our state and functions will live here...
})();
Inside it, we’ll keep our actually Signal object: the reactive data we use in our app.
// Create a data store
let todos = (function () {
// The reactive data
let state = signal(JSON.parse(localStorage.getItem('todos-preact')) || []);
})();
What we’re doing here is scoping our state inside the function.
Adding methods
Next, let’s add methods to get and modify our state
.
First, we’ll include a get()
function that returns the state.value
.
// Reactive todo data
let state = signal(JSON.parse(localStorage.getItem('todos-preact')) || []);
/**
* Get the todo list
* @return {Array} the todolist items
*/
function get () {
return state.value;
}
Next, we’ll include an add()
function. It accepts the todo
item to add as an argument.
We’ll copy/paste the code to add a todo into it, and modify the variables to fit our updated code.
/**
* Add an item to the todo list
* @param {String} todo The item to add
*/
function add (todo) {
state.value = [...state.value, todo];
}
Then, we’ll add a remove()
function that accepts the index
of the todo item to remove as an argument.
We’ll again copy/paste our code and update variable names.
/**
* Remove an item from the todo list
* @param {Integer} index The index of the todo list item to remove
*/
function remove (index) {
let arr = [...state.value];
arr.splice(index, 1);
state.value = arr;
}
Finally, we’ll return an object with our three methods.
// Create a data store
let todos = (function () {
// Reactive todo data
let state = signal(JSON.parse(localStorage.getItem('todos-preact')) || []);
// ...
return {get, add, remove};
})();
When the IIFE runs, it returns an object of functions that have access to the state
object and can modify it for us, but block us from modifying the Signal directly.
Using our store
Now, we can add todos like this…
// Otherwise, add todo
todos.add(form.todo.value);
And remove them like this…
// Otherwise, remove the todo
todos.remove(index);
Inside our template, we’ll use the todos.get()
function to get the array of items instead of todos.value
.
// Otherwise, render the todo items
return html`
<ul>
${todos.get().map(function (todo, index) {
return html`<li>${todo} <button data-delete="${index}">Delete</button></li>`;
})}
</ul>`;
Using a store results in cleaner, easier-to-read code that’s less likely to throw errors.