Détail du package

@preact/signals-react

preactjs513.9kMIT3.2.1

Manage state with style in React

readme

Signals

Signals is a performant state management library with two primary goals:

  1. Make it as easy as possible to write business logic for small up to complex apps. No matter how complex your logic is, your app updates should stay fast without you needing to think about it. Signals automatically optimize state updates behind the scenes to trigger the fewest updates necessary. They are lazy by default and automatically skip signals that no one listens to.
  2. Integrate into frameworks as if they were native built-in primitives. You don't need any selectors, wrapper functions, or anything else. Signals can be accessed directly and your component will automatically re-render when the signal's value changes.

Read the announcement post to learn more about which problems signals solves and how it came to be.

React Integration

The React integration can be installed via:

npm install @preact/signals-react

We have a couple of options for integrating Signals into React. The recommended approach is to use the Babel transform to automatically make your components that use signals reactive.

Babel Transform

Install the Babel transform package (npm i --save-dev @preact/signals-react-transform) and add the following to your Babel config:

{
    "plugins": [["module:@preact/signals-react-transform"]]
}

This will automatically transform your components to be reactive. You can then use signals directly inside your components.

import { signal } from "@preact/signals-react";

const count = signal(0);

function CounterValue() {
    // Whenever the `count` signal is updated, we'll
    // re-render this component automatically for you
    return <p>Value: {count.value}</p>;
}

See the Readme for the Babel plugin for more details about how the transform works and configuring it.

useSignals hook

If you can't use the Babel transform, you can directly call the useSignals hook to make your components reactive.

import { useSignals } from "@preact/signals-react/runtime";

const count = signal(0);

function CounterValue() {
    useSignals();
    return <p>Value: {count.value}</p>;
}

Hooks

If you need to instantiate new signals or create new side effects on signal changes inside your components, you can use the useSignal, useComputed and useSignalEffect hooks.

import { useSignal, useComputed, useSignalEffect } from "@preact/signals-react";

function Counter() {
    const count = useSignal(0);
    const double = useComputed(() => count.value * 2);

    useSignalEffect(() => {
        console.log(`Value: ${count.value}, value x 2 = ${double.value}`);
    });

    return (
        <button onClick={() => count.value++}>
            Value: {count.value}, value x 2 = {double.value}
        </button>
    );
}

Using signals with React's SSR APIs

Components rendered using SSR APIs (e.g. renderToString) in a server environment (i.e. an environment without a global window object) will not track signals used during render. Components generally don't rerender when using SSR APIs so tracking signal usage is useless since changing these signals can't trigger a rerender.

Rendering optimizations

The React adapter ships with several optimizations it can apply out of the box to skip virtual-dom rendering entirely. If you pass a signal directly into JSX, it will bind directly to the DOM Text node that is created and update that whenever the signal changes.

import { signal } from "@preact/signals-react";

const count = signal(0);

// Unoptimized: Will trigger the surrounding
// component to re-render
function Counter() {
    return <p>Value: {count.value}</p>;
}

// Optimized: Will update the text node directly
function Counter() {
    return (
        <p>
            <>Value: {count}</>
        </p>
    );
}

To opt into this optimization, simply pass the signal directly instead of accessing the .value property.

Note The content is wrapped in a React Fragment due to React 18's newer, more strict children types.

Utility Components and Hooks

The @preact/signals-react/utils package provides additional utility components and hooks to make working with signals even easier.

Show Component

The Show component provides a declarative way to conditionally render content based on a signal's value.

import { Show } from "@preact/signals-react/utils";
import { signal } from "@preact/signals-react";

const isVisible = signal(false);

function App() {
    return (
        <Show when={isVisible} fallback={<p>Nothing to see here</p>}>
            <p>Now you see me!</p>
        </Show>
    );
}

// You can also use a function to access the value
function App() {
    return <Show when={isVisible}>{value => <p>The value is {value}</p>}</Show>;
}

For Component

The For component helps you render lists from signal arrays with automatic caching of rendered items.

import { For } from "@preact/signals-react/utils";
import { signal } from "@preact/signals-react";

const items = signal(["A", "B", "C"]);

function App() {
    return (
        <For each={items} fallback={<p>No items</p>}>
            {(item, index) => <div key={index}>Item: {item}</div>}
        </For>
    );
}

Additional Hooks

useLiveSignal

The useLiveSignal hook allows you to create a local signal that stays synchronized with an external signal.

import { useLiveSignal } from "@preact/signals-react/utils";
import { signal } from "@preact/signals-react";

const external = signal(0);

function Component() {
    const local = useLiveSignal(external);
    // local will automatically update when external changes
}
useSignalRef

The useSignalRef hook creates a signal that behaves like a React ref with a .current property.

import { useSignalRef } from "@preact/signals-react/utils";

function Component() {
    const ref = useSignalRef(null);
    return <div ref={ref}>The ref's value is {ref.current}</div>;
}

Limitations

This version of React integration does not support passing signals as DOM attributes. Support for this may be added at a later date.

Using signals in render props is not recommended. In this situation, the component that reads the signal is the component that calls the render prop, which may or may not be hooked up to track signals. For example:

const count = signal(0);

function ShowCount({ getCount }) {
    return <div>{getCount()}</div>;
}

function App() {
    return <ShowCount getCount={() => count.value} />;
}

Here, the ShowCount component is the one that accesses count.value at runtime since it invokes getCount, so it needs to be hooked up to track signals. However, since it doesn't statically access the signal, the Babel transform won't transform it by default. One fix is to set mode: all in the Babel plugin's config, which will transform all components. Another workaround is put the return of the render prop into it's own component and then return that from your render prop. In the following example, the Count component statically accesses the signal, so it will be transformed by default.

const count = signal(0);

function ShowCount({ getCount }) {
    return <div>{getCount()}</div>;
}

const Count = () => <>{count.value}</>;

function App() {
    return <ShowCount getCount={() => <Count />} />;
}

Similar issues exist with using object getters & setters. Since the it isn't easily statically analyzable that a getter or setter is backed by a signal, the Babel plugin may miss some components that use signals in this way. Similarly, setting Babel's plugin to mode: all will fix this issue.

License

MIT, see the LICENSE file.

changelog

@preact/signals-react

3.2.1

Patch Changes

  • #711 4b96199 Thanks @calebeby! - Narrow types for Show utility, the callback is truthy by design

  • Updated dependencies [4045d2d]:

    • @preact/signals-core@1.11.0

3.2.0

Minor Changes

Patch Changes

3.1.1

Patch Changes

3.1.0

Minor Changes

3.0.1

Patch Changes

  • #640 503128f Thanks @andrewiggins! - Don't track signals read during an SSR render (e.g. renderToString, renderToStaticMarkup)

3.0.0

Major Changes

2.3.0

Minor Changes

Patch Changes

  • #611 57a7d38 Thanks @Xstoudi! - Silences noisy warnings about useLayoutEffect whilst using SSR by switching to an isomorphic layout effect hook

  • #624 18b2f29 Thanks @JoviDeCroock! - Fix the stubbed ReactElementType to use the newly added traditional element in v19

2.2.0

Minor Changes

2.1.0

Minor Changes

  • #578 931404e Thanks @JoviDeCroock! - Allow for passing no argument to the signal and the type to be automatically inferred as T | undefined

Patch Changes

2.0.2

Patch Changes

  • #570 d653451 Thanks @developit! - Fix out-of-order effect error when suspending in React Native

  • Updated dependencies [c8c95ac]:

    • @preact/signals-core@1.6.1

2.0.1

Patch Changes

2.0.0

Major Changes

  • #467 d7f43ef Thanks @andrewiggins! - Remove auto tracking using React internals from signals-react package

    Before this change, importing @preact/signals-react would invoke side effects that hook into React internals to automatically track signals. This change removes those side effects and requires consumers to update their code to continue using signals in React.

    We made this breaking change because the mechanism we were using to automatically track signals was fragile and not reliable. We've had multiple issues reported where signals were not being tracked correctly. It would also lead to unexpected errors that were hard to debug.

    For some consumers and apps though, the current mechanism does work. If you'd like to continue using this mechanism, simply add import "@preact/signals/auto"; to the root of your app where you call ReactDOM.render. For our newly supported ways of using signals in React, check out the new Readme for @preact/signals-react.

1.3.8

Patch Changes

  • #456 b0b2a5b Thanks @XantreGodlike! - Ensure types are resolved against built .d.ts rather than source .ts

  • Updated dependencies [990f1eb]:

    • @preact/signals-core@1.5.1

1.3.7

Patch Changes

1.3.6

Patch Changes

1.3.5

Patch Changes

1.3.4

Patch Changes

1.3.3

Patch Changes

1.3.2

Patch Changes

1.3.1

Patch Changes

1.3.0

Minor Changes

Patch Changes

1.2.2

Patch Changes

  • #243 e41b8b1 Thanks @melnikov-s! - Replace Map useage with WeakMap

  • #282 cafbdaa Thanks @developit! - Fix a bug that caused cleanup functions returned from a useSignalEffect() callback not to be called.

  • Updated dependencies [7e15d3c]:

    • @preact/signals-core@1.2.3

1.2.1

Patch Changes

  • #238 bcf4b0b Thanks @eddyw! - Fix ERR_UNSUPPORTED_DIR_IMPORT error when importing use-sync-external-store/shim from ESM build

1.2.0

Minor Changes

Patch Changes

1.1.1

Patch Changes

1.1.0

Minor Changes

  • #91 fb74bb9 Thanks @JoviDeCroock! - add the useSignalEffect hook

  • #183 79ff1e7 Thanks @jviide! - Add ability to run custom cleanup logic when an effect is disposed.

    effect(() => {
      console.log("This runs whenever a dependency changes");
      return () => {
        console.log("This runs when the effect is disposed");
      });
    });
    

Patch Changes

  • #161 6ac6923 Thanks @jviide! - Remove all usages of Set, Map and other allocation heavy objects in signals-core. This substaintially increases performance across all measurements.

  • Updated dependencies [b4611cc, 9802da5, 6ac6923, 79ff1e7, 3e31aab]:

    • @preact/signals-core@1.2.0

1.0.2

Patch Changes

1.0.1

Patch Changes

  • 62439c9: Fixes invalid React peer dependency range for environments with strict peerDeps
  • Updated dependencies [336bb34]
  • Updated dependencies [bc0080c]
  • Updated dependencies [7228418]
  • Updated dependencies [32abe07]
  • Updated dependencies [4782b41]
  • Updated dependencies [bf6af3b]
    • @preact/signals-core@1.1.0

1.0.0

Major Changes

  • 2ee8489: The v1 release for the signals package, we'd to see the uses you all come up with and are eager to see performance improvements in your applications.

Patch Changes

  • Updated dependencies [ab22ec7]
  • Updated dependencies [2ee8489]
  • Updated dependencies [b56abf3]
    • @preact/signals-core@1.0.0

0.0.2

Patch Changes

  • Updated dependencies [702a9c5]
    • @preact/signals-core@0.0.5