As a React developer for 5 years, I have challenges managing state. React hooks improved the situation, but dealing with nested hooks, manual caching, and complex logic was still a struggle. Then came @preact/signals-react
, a little-known library that turned my development chaos into a more peaceful and organized experience.
Getting stuck in the world of React hooks had its exciting beginning. The idea of managing the state functionally brought a sense of accomplishment. However, as projects expanded, so did the complications with hooks. Dealing with nested useEffect hooks felt like navigating through dense jungles, conditional rendering logic turned messy, and the constant threat of re-renders impacted performance and sanity.
I discovered @preact/signals-react
, and at first, it appeared almost too good to be true. “Managing state without hooks? Can that really work?” However, as I explored further, a feeling of relief came over me. It wasn’t some magic trick; it was a whole new way of doing things, centered around reactive data streams known as @preact/signals-react
.
Here’s what blew me away:
Performance optimization: @preact/signals-react
helps reduce unnecessary re-renders of components, which leads to faster app loading, smoother interactions, and better overall.
Simplified State Management: In small to large-sized apps, @preact/signals-react
can often replace more complex state management libraries, making your code easier to understand and maintain.
Improve code organization: @preact/signals-react
keeps track of data changes clearly and predictably, making our code more organized and less prone to error.
Let’s jump into a small example,
React code using regular hooks
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/data')
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
});
}, []);
if (loading) {
return <p>Loading...</p>;
}
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
The same code using @preact/signals-react
const data = useSignal(() =>
fetch('/api/data')
.then(response => response.json())
);
const loading = useSignal(() => data.loading);
return (
<ul>
{loading.get() && <p>Loading...</p>}
{data.get().map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
Look at how the @preact/signals-react
code is shorter, clearer, and simpler. You set up @preact/signals-react
for data and loading, and their related values update automatically. No tangled hooks, no intricate conditionals — just straightforward, declarative code.
Signals also enhance performance by default. That’s not the only thing @preact/signals-react
do. At this point, you don’t really need to memorize React hooks, like memo, useMemo, and all that stuff you would normally need. And even manual caching.
If you’ve worked with React, you’ve probably encountered the problem of unnecessary re-renders.
Here, a simple react application using hooks.
// app.jsx
import React, { useState } from 'react';
import { ChildOne } from './child-one';
import { ChildTwo } from './child-two';
const App = () => {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
};
return (
<div>
<button onClick={incrementCount}>Increment Count</button>
<ChildOne count={count} />
<ChildTwo />
</div>
);
}
export default App;
// child-one.jsx
const ChildOne = ({ count }) => {
console.log('ChildComponent 1 is rendered');
return <div>{`Count: ${count}`}</div>
}
// child-two.jsx
const ChildTwo = ({ count }) => {
console.log('ChildComponent 2 is rendered');
return <p>Child 2</p>
}
When we look at the console, we see that both Child-One and Child-Two components re-render every time we click the ‘Increment Count’ button. Based on the logic, the re-rendering of Child-Two each time is unnecessary.
So, by using @preact/signals-react
, we can prevent this unnecessary re-rendering issue.
First, you need to install @preact/signals-react
npm i @preact/signals-react
// app.jsx
import React from 'react';
import { signal } from '@preact/signals-react';
import { ChildOne } from './child-one';
import { ChildTwo } from './child-two';
const count = signal(0);
const App = () => {
const incrementCount = () => {
count.value += 1;
};
return (
<div>
<button onClick={incrementCount}>Increment Count</button>
<ChildOne count={count} />
<ChildTwo />
</div>
);
}
export default App;
In this example, the child components are the same as before. The key difference is that we’re using signals
instead of hooks
. If you notice, when we click the “Increment Count” button, there’s no console log in the browser. This means we’ve successfully avoided unnecessary re-rendering using @preact/signals-react
.
Let’s see behind the scenes of the @preact/signals-react
.
The below image shows a DevTools Profiler trace for the same app measured twice: once using hooks as the state primitive and a second time using signals
Useful Links
Read More: https://preactjs.com/blog/introducing-signals
Get Started: https://www.npmjs.com/package/@preact/signals-react
Code Sample: https://github.com/chanakaHetti/react-signals-research
Happy coding 🙂