As React developers, we know how hard it can be to control deeply nested child components from a parent component. Several tools and methodologies are available for doing this. Some of these techniques include passing controlled state down explicitly via props (the dreaded prop drilling approach), Compositioning (Using the children prop), enclosing a context over the children elements(createContext & useContext) & Global state management by using stage management libraries such as Redux or Mobx.
All the approaches mentioned above are valid solutions to the problem posed above. The only issue is most of the solutions involve either a lot of code restructuring and manual intervention(in the case of prop drilling and composition) or boilerplate code scaffolding(In the case of using context or most global state management libraries).
With signals, Controlling such states becomes trivial and developer friendly but do note that there are certain caveats to this approach if you use React's latest concurrency features in your code base. At this point, there are no official exports from the react library that fully supports signal-based state updates. As a result, we'll use the lightweight version of react known as Preact that offers this capability.
Using Preact Signals in React
Let's start by creating a new react project. I'll scaffold a new project using Vite as the build tool and pnpm as the package manager. You may follow along with the aforementioned tools or any tool that you are comfortable using.
Run the following in your shell:
pnpm create vite
It will show a few prompts like the library to install(choose React) & whether to use typescript or not(I've chosen to use typescript with swc compiler). Finally, Run pnpm install to install all dependencies into node_modules.
To start the development server with the boilerplate code introduced by vite, Execute the following in your shell:
pnpm dev
To use react signals we'll have to install the preact signals package from pnpm. In your shell, run:
pnpm i @preact/signals-react
Once this package gets installed, We can start using signal-based state variables by importing the
signal function from the preact signals package as follows:
GlobalState.ts
import { signal } from "@preact/signals-core";
To create a global state variable, Do the following in the global scope of your file(Basically not inside your React component code):
GlobalState.ts
import { signal } from "@preact/signals-core";
const theme = signal('light');
Now you can use this in your components and change values by direct mutation(there's no need to return new values in a setter function as the update is handled internally by proxies). We can do one better. Instead of using the global state variable directly, we can use the computed function provided by the preact-signals library to derive the state and use it in our components as follows:
GlobalState.ts
const theme = signal('light');
import { computed, signal } from "@preact/signals-react";
const currentTheme = computed(() => theme.value);
export default currentTheme;
Now it's only a matter of importing the currentTheme in any of your tsx files and embedding it into the JSX using the curly brace notation({currentTheme}).
Somefile.tsx
<div>{currentTheme}</div>
To make changes to the theme state variable, You can either directly export and import the theme global state variable or preferably create helper functions to restrict the usage of the theme state variable. For example:
GlobalState.ts
const theme = signal('light');
import { computed, signal } from "@preact/signals-react";
const currentTheme = computed(() => theme.value);
export const toggleTheme = () => (
currentTheme.value === 'Light' ?
theme.value = 'Dark'
:
theme.value = 'Light'
);
export default currentTheme;
To use toggleTheme function, Import it in your tsx files and call it like any normal function.
Somefile.tsx
<button onClick={toggleTheme}>Toggle Theme<button>
Just by looking at the sample code, it is quite evident that using and manipulating global state variables becomes a lot easier plus there are no performance side effects related to unnecessary rerenders as signal-based state change cause the affected part of the DOM tree to get the new state without causing the entire component to rerender.
Final Code
App.tsx
GlobalState.ts
ShowCurrentTheme.tsx
ToggleThemeButton.tsx
Many other use cases and advanced scenarios can leverage the various other exports from the preact-signals package. There are hooks variant to create signal-based state within local to your function component if you do not want it to be globally accessible with the added benefit of being more performant with updates. Reading the preact signals documentation for more complex use cases and guidelines on best practices is a necessary first step in your journey to learn and integrate signal-based state management into your codebase. You can checkout the above code at this link.
Conclusion
React signals are a powerful tool for React developers that can help create more efficient and maintainable code. By using React signals, you can create loosely coupled components that communicate with each other without relying on props or state. This can lead to better performance, improved code readability, and easier maintenance over time. If you are looking for a way to improve your React applications, consider using React signals in your code.