Skip to main content Accessibility Feedback

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.

Here’s a demo for you.