npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@watch-state/react

v2.1.1

Published

State manager of React

Readme

  @watch-state/react

 

NPM downloads downloads types changelog license tests

@watch-state/react provides React hooks for watch-state — a lightweight, high-performance reactive state engine.

Written in TypeScript and provides full type definitions out of the box.

stars watchers

Index

[ Install ]
[ Hooks ] useObservableuseSelectoruseNewStateuseNewCompute
[ Utils ] subscribe
[ Examples ] Aside MenuTodo ListAsync
[ Links ]
[ Issues ]

Install

🏠︎ / Install

Requires React 18+ and watch-state 3.5+.

Use with any modern bundler (Vite, Webpack, Rollup, etc.) or framework (Next.js, Remix, etc.).

npm i @watch-state/react

With watch-state

npm i watch-state @watch-state/react

Hooks

🏠︎ / Hooks

useObservableuseSelectoruseNewStateuseNewCompute

useObservable

🏠︎ / Hooks / useObservable

Subscribe React components to watch-state changes with automatic re-render optimization.

Uses useSyncExternalStore for correct synchronization with React. Automatically subscribes via Watch and unsubscribes on unmount.

Watching Observables

Pass a State instance (or any Observable subclass, such as Compute) to useObservable() to subscribe to its changes. The hook returns the current value and triggers a re-render whenever the observable value changes.

import { State } from 'watch-state'
import { useObservable } from '@watch-state/react'

const $count = new State(0)

const increase = () => {
  $count.value++
}

function Button () {
  const count = useObservable($count)

  return <button onClick={increase}>{count}</button>
}

Batching Observables

This example demonstrates batching multiple state updates into one reactive event with createEvent. Clicking the button increments $a and $b atomically; the computed $sum then updates reactively without intermediate renders since all changes occur in a single update cycle.

import { State, Compute, createEvent } from 'watch-state'
import { useObservable } from '@watch-state/react'

const $a = new State(1)
const $b = new State(2)
const $sum = new Compute(() => $a.value + $b.value)

const increase = createEvent(() => {
  $a.value++
  $b.value++
})

function Button () {
  const sum = useObservable($sum)

  return <button onClick={increase}>{sum}</button>
}

useSelector

🏠︎ / Hooks / useSelector

You can pass a function to useSelector() to create a reactive selector that triggers re-renders only when the returned value changes (compared with Object.is).

The function may be called multiple times during a single render, so it must be pure and simple.

Uses useSyncExternalStore for correct synchronization with React.

Extracting Fields

This is ideal for lightweight, pure selections — e.g. extracting a field.

import { State } from 'watch-state'
import { useSelector } from '@watch-state/react'

const $user = new State({ name: 'Mike', age: 42 })

function UserName () {
  const name = useSelector(() => $user.value.name)

  return <div>Hello, {name}!</div>
}

Combining Multiple Observables

You can combine multiple observables in one selector — the result updates reactively when any dependency changes.

import { State } from 'watch-state'
import { useSelector } from '@watch-state/react'

const $price = new State(100)
const $quantity = new State(2)

function Total () {
  const total = useSelector(() => $price.value * $quantity.value)

  return <div>Total: ${total}</div>
}

Use Compute for complex computations or when returning new objects/arrays to avoid unnecessary recalculations or re-renders.

import { Compute, State } from 'watch-state'
import { useObservable } from '@watch-state/react'

const $products = new State(['apple', 'banana', 'cherry'])

const $list = new Compute(() => {
  return $products.value.map(product => product.toUpperCase())
})

function Total () {
  const list = useObservable($list)

  return (
    <ul>
      {list.map((value, index) => (
        <li key={index}>{value}</li>
      ))}
    </ul>
  )
}

Combining with React State or Props

You can combine useSelector() with React's useState or props to react to both watch-state changes and component state.

import { useState } from 'react'
import { State } from 'watch-state'
import { useSelector } from '@watch-state/react'

const $basePrice = new State(100)

function ProductCard ({ isMember }: { isMember: boolean }) {
  const [quantity, setQuantity] = useState(1)

  const total = useSelector(() => {
    return $basePrice.value * quantity * (isMember ? 0.9 : 1)
  })

  return (
    <div>
      <p>Total: ${total}</p>
      <button onClick={() => setQuantity(q => q + 1)}>+</button>
    </div>
  )
}

Optimizing Expensive Computations with useMemo

For expensive computations or when returning new objects/arrays, use useMemo to avoid unnecessary recalculations.

import { State } from 'watch-state'
import { useObservable } from '@watch-state/react'
import { useMemo } from 'react'

const $items = new State(['apple', 'banana', 'cherry'])

function PrefixedItems ({ prefix }: { prefix: string }) {
  const items = useObservable($items)

  const prefixedItems = useMemo(() => {
    return items.map(item => `${prefix} - ${item}`)
  }, [items, prefix])

  return <div>{prefixedItems.join(', ')}</div>
}

useNewState

🏠︎ / Hooks / useNewState

Create a State instance inside a React component that persists across re-renders and can be watched using useObservable.

import { Observable } from 'watch-state'
import { useObservable, useNewState } from '@watch-state/react'

function Parent () {
  const $count = useNewState(0)

  const handleClick = () => {
    $count.value++
  }

  return (
    <div>
      <button onClick={handleClick}>+</button>
      <Child $count={$count} />
    </div>
  )
}

function Child ({ $count }: { $count: Observable<number> }) {
  const count = useObservable($count)

  return <div>{count}</div>
}

This example demonstrates a key optimization: when the button is clicked and $count.value changes, only the Child component re-renders (because it's subscribed to the observable), while the Parent component remains unchanged. This happens because useNewState creates a reactive state that doesn't trigger re-renders in the component where it's defined—only components that explicitly subscribe via useObservable or useSelector will re-render when the state changes.

Using Context for State Sharing

You can also use React Context to share reactive state across deeply nested components without prop drilling:

import { createContext, useContext } from 'react'
import { Observable } from 'watch-state'
import { useObservable, useNewState } from '@watch-state/react'

const CountContext = createContext<Observable<number> | undefined>(undefined)

const useCount = () => {
  const $count = useContext(CountContext)

  if (!$count) throw new Error('CountContext must be provided')

  return useObservable($count)
}

function Parent () {
  const $count = useNewState(0)

  const handleClick = () => {
    $count.value++
  }

  return (
    <CountContext.Provider value={$count}>
      <button onClick={handleClick}>+</button>
      <Child />
    </CountContext.Provider>
  )
}

function Child () {
  const count = useCount()

  return <div>{count}</div>
}

useNewCompute

🏠︎ / Hooks / useNewCompute

Create a Compute instance inside a React component (persists across re-renders) that can be watched using useObservable.

import { useObservable, useNewCompute, useNewState } from '@watch-state/react'

function Parent () {
  const $name = useNewState('Foo')
  const $surname = useNewState('Bar')

  const $fullName = useNewCompute(() => (
    `${$name.value} ${$surname.value[0]}.`
  ))

  const fullName = useObservable($fullName)

  const handleClick = () => {
    $surname.value = 'Baz'
  }

  return <button onClick={handleClick}>{fullName}</button>
}

When the button is clicked, the component will not re-render even though $surname changed, because the computed value $fullName remains the same ("Foo B." before and after the change). This demonstrates the automatic optimization of useNewCompute - components only re-render when the computed value actually changes.

Using Props for Compute Sharing

You can pass a computed observable as a prop to child components. The Parent creates a Compute via useNewCompute and passes it down; only the Child re-renders when the computed value changes, while the Parent stays untouched.

import { Observable, State } from 'watch-state'
import { useObservable, useNewCompute } from '@watch-state/react'

const $name = new State('Mike')
const $surname = new State('Deight')

function Parent () {
  const $fullName = useNewCompute(() => `${$name.value} ${$surname.value[0]}.`)

  return <Child $fullName={$fullName} />
}

function Child ({ $fullName }: { $fullName: Observable<string> }) {
  const fullName = useObservable($fullName)

  return <div>{fullName}</div>
}

Using dependency array for component state

Pass a dependency array as the second argument to useNewCompute to incorporate non-reactive values (props, React state) into the compute function. When any dependency in the array changes, the existing Compute instance triggers an update — recalculating its value without being recreated.

import { Observable, State } from 'watch-state'
import { useObservable, useNewCompute } from '@watch-state/react'

const $name = new State('Mike')

function Parent ({ surname }: { surname: string }) {
  const $fullName = useNewCompute(() => (
    `${$name.value} ${surname[0]}.`
  ), [surname])

  return <Child $fullName={$fullName} />
}

function Child ({ $fullName }: { $fullName: Observable<string> }) {
  const fullName = useObservable($fullName)

  return <div>{fullName}</div>
}

Using Context for Compute Sharing

You can use React Context to share a computed observable across deeply nested components without prop drilling:

import { createContext, useContext } from 'react'
import { Observable, State } from 'watch-state'
import { useObservable, useNewCompute } from '@watch-state/react'

const $name = new State('Mike')
const $surname = new State('Deight')

const FullNameContext = createContext<Observable<string> | undefined>(undefined)

const useFullName = () => {
  const $fullName = useContext(FullNameContext)

  if (!$fullName) throw new Error('FullNameContext must be provided')

  return useObservable($fullName)
}

function Parent () {
  const $fullName = useNewCompute(() => `${$name.value} ${$surname.value[0]}.`)

  return (
    <FullNameContext.Provider value={$fullName}>
      <Child />
    </FullNameContext.Provider>
  )
}

function Child () {
  const fullName = useFullName()

  return <div>{fullName}</div>
}

Utils

🏠︎ / Utils

subscribe

subscribe

🏠︎ / Utils / subscribe

Stable subscription factory for useSyncExternalStore with watch-state.

Used internally by useObservable and useSelector. Creates a Watch instance that calls the provided callback on state changes.

import { useSyncExternalStore } from 'react'
import { subscribe } from '@watch-state/react'
import { State } from 'watch-state'

const $state = new State(0)

const value = useSyncExternalStore(subscribe, () => $state.value)
// Same as useObservable($state)

Examples

🏠︎ / Examples

Aside MenuTodo ListAsync

Aside Menu

🏠︎ / Examples / Aside Menu

Two independent components sharing a single global State. The button toggles $show, while AsideMenu subscribes via useObservable and re-renders accordingly — the button itself never re-renders.

import { State } from 'watch-state'
import { useObservable } from '@watch-state/react'

const $show = new State(false)

function AsideMenuButton () {
  const toggle = () => {
    $show.value = !$show.value
  }

  return <button onClick={toggle} />
}

function AsideMenu () {
  const show = useObservable($show)

  return show ? <div>Aside Menu</div> : null
}

Todo List

🏠︎ / Examples / Todo List

A classic todo app showing how to combine a global reactive State with React's local useState. The todo list is shared via useObservable, while the input field stays in component-local state.

import { useState } from 'react'
import { State } from 'watch-state'
import { useObservable } from '@watch-state/react'

interface Todo {
  id: number
  text: string
  done: boolean
}

const $todos = new State<Todo[]>([])
let nextId = 1

const addTodo = (text: string) => {
  $todos.value.push({ id: nextId++, text, done: false })
  $todos.update()
}

const toggleTodo = (todoId: number) => {
  $todos.value = $todos.value.map(todo =>
    todoId === todo.id ? { ...todo, done: !todo.done } : todo
  )
}

function TodoList () {
  const todos = useObservable($todos)
  const [text, setText] = useState('')

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()

    if (text.trim()) {
      addTodo(text.trim())
      setText('')
    }
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          value={text}
          onChange={e => setText(e.target.value)}
          placeholder="What needs to be done?"
        />
        <button type="submit">Add</button>
      </form>
      <ul>
        {todos.map(({ id, done, text }) => (
          <li
            key={id}
            onClick={() => toggleTodo(id)}
            style={{ textDecoration: done ? 'line-through' : 'none' }}
          >
            {text}
          </li>
        ))}
      </ul>
    </div>
  )
}

Async

🏠︎ / Examples / Async

This example demonstrates integration with @watch-state/async for reactive data fetching. Async is an observable that wraps a Promise-returning function. Use useObservable to subscribe to the resolved value and useSelector to reactively track its loading, loaded, and error properties.

import { useObservable, useSelector } from '@watch-state/react'
import Async from '@watch-state/async'

const $api = new Async(
  () => fetch('/api/test')
    .then(r => r.json())
)

function User () {
  const value = useObservable($api)
  const loading = useSelector(() => $api.loading)
  const loaded = useSelector(() => $api.loaded)
  const error = useSelector(() => $api.error)

  if (error) {
    return <div>Error!</div>
  }

  if (!loaded) {
    return <div>Loading</div>
  }

  return (
    <div className={loading && 'loading'}>
      {value.some.fields}
    </div>
  )
}

Links

🏠︎ / Links

Issues

🏠︎ / Issues

If you find a bug or have a suggestion, please file an issue on GitHub

issues