Basics Signal is a reactive value which re-renders the component when it is changed signal.value consumed by component re-renders the component if we consume signal directly in jsx, it will just change the dom without component re-render to update the signal you just have to mutate the signal.value prop, that's simple https://preactjs.com/guide/v10/signals/ https://github.com/preactjs/signals/tree/main/packages/react#react-integration npm install @preact/signals-react to let it signals without react hooks some magic is done on compilation step, thus we need to add Babel transformation npm i --save-dev @preact/signals-react-transform Vite config Add ["module:@preact/signals-react-transform"] into babel plugins /// <reference types="vitest" /> /// <reference types="vite/client" /> import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' import tsconfigPaths from 'vite-tsconfig-paths' // https://vitejs.dev/config/ export default defineConfig(({ command, mode }) => { // Load env file based on `mode` in the current working directory. // Set the third parameter to '' to load all env regardless of the `VITE_` prefix. const env = loadEnv(mode, process.cwd(), '') return { server: { port: Number(env.PORT_FRONT_END), proxy: { // '/api': `${process.env.DOMAIN}:${process.env.PORT_BACK_END}/` '/api': `${env.DOMAIN}:${env.PORT_BACK_END}`, }, // hmr: { // host: 'localhost', // port: Number(env.PORT_BACK_END), // } }, esbuild: { define: { // to suppress warning in terminal: [vite] warning: Top-level "this" will be replaced with undefined since this file is an ECMAScript module this: 'window', }, }, plugins: [ react({ // to show readable class names in styled components with vite // https://github.com/styled-components/babel-plugin-styled-components/issues/350#issuecomment-979873241 jsxImportSource: '@emotion/react', babel: { plugins: [ [ // 'babel-plugin-styled-components', '@emotion/babel-plugin', { displayName: true, fileName: true, }, ], // https://github.com/preactjs/signals/tree/main/packages/react#react-integration ["module:@preact/signals-react-transform"], ], }, }), // https://github.com/aleclarson/vite-tsconfig-paths tsconfigPaths(), ], // https://vitest.dev/guide/in-source.html define: { 'import.meta.vitest': 'undefined', }, // https://www.youtube.com/watch?v=oWJpxtAl62w test: { globals: true, environment: 'jsdom', setupFiles: './test-setup.ts', includeSource: ['client/**/*.{js,ts,jsx,tsx}'], coverage: { all: true, src: ['client/'], }, }, build: { outDir: 'build', }, } }) Example of reactive global state // bottomMessageSignal.ts import { signal } from '@preact/signals-react' export const bottomMessage = signal('') export const showBottomMessage = (msg: string): void => { bottomMessage.value = msg } export const hideBottomMessage = (): void => { bottomMessage.value = '' } // BottomMessage.tsx import { AnimatePresence, motion } from 'framer-motion' import { bottomMessage } from './bottomMessageSignal' export const BottomMessage = (): JSX.Element => { return ( <AnimatePresence> {bottomMessage.value !== '' && ( <motion.span initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 20 }} transition={{ duration: 0.5 }} css={{ position: 'fixed', bottom: 5, right: 5, fontSize: 14, color: '#828282', fontWeight: 500, userSelect: 'none', }} > {bottomMessage.value} </motion.span> )} </AnimatePresence> ) }