Gathering detailed insights and metrics for @preact-signals/safe-react
Gathering detailed insights and metrics for @preact-signals/safe-react
Gathering detailed insights and metrics for @preact-signals/safe-react
Gathering detailed insights and metrics for @preact-signals/safe-react
Preact Signals: Supercharge your React/Preact development! Unleash the power of reactive programming with hooks, components, a TanStack query adapter, and more. Code smarter, not harder
npm install @preact-signals/safe-react
Typescript
Module System
Node Version
NPM Version
69.8
Supply Chain
96.5
Quality
81
Maintenance
100
Vulnerability
97
License
@preact-signals/unified-signals@0.3.1
Published on 21 Nov 2024
@preact-signals/utils@0.23.1
Published on 21 Nov 2024
@preact-signals/safe-react@0.8.1
Published on 21 Nov 2024
@preact-signals/query@2.1.1
Published on 21 Nov 2024
@preact-signals/unified-signals@0.3.0
Published on 16 Nov 2024
@preact-signals/utils@0.23.0
Published on 16 Nov 2024
TypeScript (93.33%)
Rust (4.67%)
JavaScript (1.46%)
CSS (0.37%)
HTML (0.1%)
Nushell (0.06%)
Total Downloads
71,034
Last Day
367
Last Week
2,066
Last Month
8,192
Last Year
67,647
76 Stars
507 Commits
2 Watching
31 Branches
1 Contributors
Minified
Minified + Gzipped
Latest Version
0.8.1
Package Id
@preact-signals/safe-react@0.8.1
Unpacked Size
2.32 MB
Size
684.78 kB
File Count
78
NPM Version
10.9.0
Node Version
23.3.0
Publised On
21 Nov 2024
Cumulative downloads
Total Downloads
Last day
-42.3%
367
Compared to previous day
Last week
-17.2%
2,066
Compared to previous week
Last month
12.8%
8,192
Compared to previous month
Last year
1,897.3%
67,647
Compared to previous year
7
2
28
1
@preact-signals/safe-react
This is community driven preact/signals integration for React, based on official @preact/signals-react
integration, since it's patching react - there are a lot of problems in different environments and bundlers. This package tries to solve this problem by this steps:
@preact/signals-react-transform
).Signals is a performant state management library with two primary goals:
Read the announcement post to learn more about which problems signals solves and how it came to be.
There are two ways of tracking signals:
automatic
- using swc/babel plugin to subscribe your components to signals (based on official @preact/signals-react-transform
).manual
- manual adding tracking to your components with HOCnext | @preact-signals/safe-react | @swc/core |
---|---|---|
^14.0.0 | 0.7.0 | - |
15.0.3 | ~0.8.0 | 1.8.0-1.9.2 |
Note: please open an issue here if in some scenario you have problems with this integration.
The React adapter allows you to access signals directly inside your components and will automatically subscribe to them.
1import { signal } from "@preact-signals/safe-react"; 2 3const count = signal(0); 4 5function CounterValue() { 6 // Whenever the `count` signal is updated, we'll 7 // re-render this component automatically for you 8 return <p>Value: {count.value}</p>; 9}
If you need to instantiate new signals inside your components, you can use the useSignal
or useComputed
hook.
1import { useSignal, useComputed } from "@preact-signals/safe-react"; 2 3function Counter() { 4 const count = useSignal(0); 5 const double = useComputed(() => count.value * 2); 6 7 return ( 8 <button onClick={() => count.value++}> 9 Value: {count.value}, value x 2 = {double.value} 10 </button> 11 ); 12}
The React adapter ships with several optimizations it can apply out of the box to minimize virtual-dom diffing. If you pass a signal directly into JSX, it will behave as component which renders value of signal.
1import { signal } from "@preact-signals/safe-react"; 2 3const count = signal(0); 4 5// Unoptimized: Will trigger the surrounding 6// component to re-render 7function Counter() { 8 return <p>Value: {count.value}</p>; 9} 10 11// Optimized: Will diff only value of signal 12function Counter() { 13 return ( 14 <p> 15 <>Value: {count}</> 16 </p> 17 ); 18}
If you pass a signal as a prop to a component, it will automatically unwrap it for you. This means you can pass signals directly to DOM elements and they will be bound to the DOM node.
1import { signal } from "@preact-signals/safe-react"; 2 3const count = signal(0); 4 5// data-count={count} will be unwrapped and equal to data-count={count.value} 6const Counter = () => <div data-count={count}>Value: {count.value}</div>;
Comparison table:
Feature | @preact/signals-react | @preact-signals/safe-react (automatic) | @preact-signals/safe-react (manual) |
---|---|---|---|
Monkey patch free | ✅ (after 2.0.0 with babel plugin) | ✅ | ✅ |
Tracking type | automatic | automatic | manual with HOC |
Hooks | ✅ | ✅ | ✅ |
Prop unwrapping | ❌ (removed in 2.0.0) | ✅(deprecated) | ❌ |
Put signal into JSX | ✅ | ✅ | ✅ |
@preact/signals-react
Ignoring updates while rendering same component in render. Since this behavior causes double or infinite rerendering in some cases.
1const A = () => { 2 const count = signal(0); 3 count.value++; 4 return <div>{count.value}</div>; 5};
1npm install @preact-signals/safe-react
Integrations:
1/** @type {import('next').NextConfig} */ 2const nextConfig = { 3 experimental: { 4 swcPlugins: [ 5 [ 6 "@preact-signals/safe-react/swc", 7 { 8 // you should use `auto` mode to track only components which uses `.value` access. 9 // Can be useful to avoid tracking of server side components 10 mode: "auto", 11 } /* plugin options here */, 12 ], 13 ], 14 }, 15}; 16 17module.exports = nextConfig;
1// vite.config.ts 2import { defineConfig } from "vite"; 3import reactSwc from "@vitejs/plugin-react-swc"; 4 5// https://vitejs.dev/config/ 6export default defineConfig({ 7 plugins: [ 8 reactSwc({ 9 plugins: [["@preact-signals/safe-react/swc", {}]], 10 }), 11 ], 12});
1// vite.config.ts 2import { defineConfig } from "vite"; 3import react from "@vitejs/plugin-react"; 4 5// https://vitejs.dev/config/ 6export default defineConfig({ 7 plugins: [ 8 react({ 9 babel: { 10 plugins: ["module:@preact-signals/safe-react/babel"], 11 }, 12 }), 13 ], 14});
1// vite.config.ts 2import { defineConfig } from "vite"; 3// can be used with swc plugin, too 4import react from "@vitejs/plugin-react"; 5import { createReactAlias } from "@preact-signals/safe-react/integrations/vite"; 6 7// https://vitejs.dev/config/ 8export default defineConfig({ 9 resolve: { 10 // add this 11 alias: [createReactAlias()], 12 }, 13 plugins: [ 14 react({ 15 // add this 16 jsxImportSource: "@preact-signals/safe-react/jsx", 17 babel: { 18 plugins: ["module:@preact-signals/safe-react/babel"], 19 }, 20 }), 21 ], 22});
Allows to transpile components that uses @useSignals
in node_modules (For example: @preact-signals/utils
)
1// vite.config.ts 2import { defineConfig } from "vite"; 3import reactSwc from "@vitejs/plugin-react-swc"; 4import { createSWCTransformDepsPlugin } from "@preact-signals/safe-react/integrations/vite"; 5 6// https://vitejs.dev/config/ 7export default defineConfig({ 8 resolve: { 9 alias: [ 10 // if some lib uses signals it's probably using `@preact/signals-react` 11 { 12 find: "@preact/signals-react", 13 replacement: "@preact-signals/safe-react", 14 }, 15 ], 16 }, 17 plugins: [ 18 createSWCTransformDepsPlugin({ 19 filter: (id) => id.includes("node_modules"), 20 }), 21 reactSwc({ 22 plugins: [["@preact-signals/safe-react/swc", {}]], 23 }), 24 ], 25});
1yarn add -D babel-plugin-module-resolver
1// babel.config.js 2module.exports = { 3 // or expo-preset or metro-react-native-babel-preset 4 presets: ["@rnx-kit/babel-preset-metro-react-native"], 5 plugins: [ 6 [ 7 "module-resolver", 8 { 9 alias: [ 10 { 11 "@preact/signals-react": "@preact-signals/safe-react", 12 }, 13 ], 14 }, 15 ], 16 "module:@preact-signals/safe-react/babel", 17 ], 18};
1import { withTrackSignals } from "@preact-signals/safe-react/manual"; 2 3const A = withTrackSignals(() => { 4 const count = signal(0); 5 count.value++; 6 return <div>{count.value}</div>; 7});
Magic contains 2 parts:
It will be transformed to:
1const sig = signal(0); 2const A = () => <div>{sig.value}</div>;
1import { useSignals } from "@preact-signals/safe-react/tracking"; 2 3const sig = signal(0); 4const A = () => { 5 const store = useSignals(); 6 try { 7 // all signals used in this function will be tracked 8 return <div>{sig.value}</div>; 9 } finally { 10 effectStore[EffectStoreFields.finishTracking](); 11 } 12};
1const sig = signal(0); 2 3// data-a={sig} will be unwrapped and equal to data-a={sig.value} 4const A = () => <div data-a={sig}>{sig.value}</div>;
Supported parsers:
Parser plugin transforms your components to subscribe to signals. It works in 3 modes:
all
(default)
transformHooks
: true
): all hooks that accesses .value
will be wrapped with try/finally block to track signalsauto
.value
access will be wrapped with try/finally block to track signalstransformHooks
true) that which contains .value
access will be wrapped with try/finally block to track signalsmanual
- none of hooks or components are tracked by default. You can use @useSignals
comment to track signals
1// @useSignals 2const Component = () => <div />
1{ 2 "plugins": [ 3 [ 4 "module:@preact-signals/safe-react/babel", 5 { 6 "mode": "manual" 7 } 8 ] 9 ] 10}
1[ 2 "@preact-signals/safe-react/swc", 3 { 4 "mode": "manual" 5 } 6]
transformHooks
- default: true
true
- transform hooks which uses .value
accessfalse
- don't transform hooks1[ 2 "@preact-signals/safe-react/swc", 3 { 4 "transformHooks": false 5 } 6]
1// will be transformed 2const A = () => <div>{sig.value}</div>; 3// will not be transformed 4const a = () => <div>{sig.value}</div>; 5// will be transformed 6/** 7 * @useSignals 8 */ 9const b = () => <div>{sig.value}</div>;
You can use @useSignals
to opt-in to tracking for a component that doesn't meet the criteria above.
Or you can use @noUseSignals
to opt-out of tracking for a component that does meet the criteria above.
Manual integration wraps your component in try/finally block via HOC. It's equal to:
1import { withTrackSignals } from "@preact-signals/safe-react/manual"; 2 3const A = withTrackSignals(() => { 4 const count = useSignal(0); 5 count.value++; 6 return <div>{count.value}</div>; 7}); 8// equal to 9import { useSignals } from "@preact-signals/safe-react/tracking"; 10 11const A = () => { 12 const store = useSignals(); 13 try { 14 // all signals used in this function will be tracked 15 const count = signal(0); 16 count.value++; 17 return <div>{count.value}</div>; 18 } finally { 19 effectStore[EffectStoreFields.finishTracking](); 20 } 21};
withTrackSignals
HOC@useSignals
to opt-in to tracking for a component that doesn't meet the criteria above.Maybe one of these should be marked as a client entry with "use client":
Some of server side component is transformed to track signals. Solutions:
use client
directive1"use client"; 2 3const A = () => <div>{sig.value}</div>;
@noUseSignals
directive`1/** 2 * @noUseSignals 3 */ 4const Page = () => ( 5 <head> 6 <title>Page title</title> 7 </head> 8);
auto
mode of plugin, to transform only components which uses .value
access. How parser plugin detects components?1/** @type {import('next').NextConfig} */ 2const nextConfig = { 3 experimental: { 4 swcPlugins: [ 5 [ 6 "@preact-signals/safe-react/swc", 7 { 8 mode: "auto", 9 }, 10 ], 11 ], 12 }, 13}; 14 15module.exports = nextConfig;
1const Page = async () => ( 2 <head> 3 <title>Page title</title> 4 </head> 5);
1/** 2 * @useSignals 3 */ 4const PureComponent = () => { 5 // prints "render" twice on client side and once on server side 6 console.log("render"); 7 8 return null; 9};
It's happens because signals tracking uses useSyncExternalStore
and for some reason it causes double rendering with Next.js strict mode. We can just to turn off strict mode in next.config.js
1module.exports = { 2 // other config 3 reactStrictMode: false, 4};
Rendered more hooks than during the previous render
This error occurs when you're using some component without hooks as render function conditionally.
1const sig = signal(0); 2const A = ({ renderButton }: { renderButton: () => JSX.Element }) => 3 sig.value % 2 ? renderButton() : <div>{sig.value}</div>; 4 5const B = () => <button>Some content</button>; 6 7<A renderButton={B}> 8sig.value++; // this will cause error
It isn't working, because transform think that B
is a component, but it's just a function. There're 3 ways to fix this:
B
to renderB
and use it as renderButton={renderB}
. Since transform transforms only function starting with capital letter.React.createElement(B)
instead of B()
@noUseSignals
directive to B
function1/** 2 * @noUseSignals 3 */ 4const B = () => <button>Some content</button>;
Error: Cannot update a component (
Component) while rendering a different component (
Component2). To locate the bad setState() call inside
Component2``This error occurs when you're updating another component in render time of another component. In most case you should ignore this message, since it's just warning
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.
MIT
, see the LICENSE file.
No vulnerabilities found.
No security vulnerabilities found.