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 🙏

© 2025 – Pkg Stats / Ryan Hefner

signaali

v0.0.2

Published

A nice abstraction that's also flexible and performant state management tool

Readme

Signaali

A nice abstraction that's also flexible and performant state management tool.

What's in a Signal

A Signal is an object that represent a piece of application state, that you can read, and to which you can subscribe to. The interface is simple. See Signal.ts for full type.

interface Signal<T> {
  get(): T
  subscribe(observer: Observer): Unsubscribe
}

type Observer = () => void
type Unsubscribe = () => void

Behind this simple interface you may have, for instance

  • A global state store, such as Redux. In fact, the Redux store is pretty much direct fit
  • A more localized state store such as an Atom
  • An external state store such as a Y.js document

In React, you can use the value of a signal using a useSignal hook.

The signal has a map function that allows you to select a part of the state similarly to Redux selectors.

Why isn't Redux enough

Redux is great!

Yet, some of my applications tend to have state somewhere else, such as Y.js documents, and it doesn't make sense to replicate all of that into the Redux store.

Also, the single-store architecture leads to essentially all of your selectors to be evaluated when anything changes. When using an Y.js document for state, I can indeed subscribe to only the necessary updates and I don't want to throw performance away. With Signal composition (later) my selectors are evaluated only if any of the actual underlying stores is updated.

Composing state with hooks

Given that I have application state in different stores anyway, I often write components that rely on more than one. In this case I can of course just use the proper hooks to get the data I need and compose that in my component like

  const userDetails: UserDetails[] = useUserDetailsHook()
  const selectedUserId: number = useSelectedUserId()
  const selectedUserDetails: UserDetails = userDetails.find(u => u.id === selectedUserId)

Works nicely. Yet, your component will render every time when either hook triggers a change. To optimize this, I may of course use React memo (not to be confused with useMemo). This though only works for components, so I would need to introduce a component just to avoid unnecessary rendering.

Signal composition

The Signal is a nice abstraction in the way that it's trivial to write a helper to combine two or more Signals into a new one, or just map() the value of a Signal to something that you actually need in you UI. In the case of two pieces of state, you might change your code into

  const userDetailsSignal: Signal<UserDetails[]> = getUserDetailsSignal()
  const selectedUserIdSignal: Signal[number] = getSelectedUserIdSignal()
  const selectedUserDetails = useSignal(combineSignals(userDetailsSignal, selectedUserIdSignal, 
    (userDetails, selectedUserId) => userDetails.find(u => u.id === selectedUserId)))

You first combine the signals with a "selector" that yields only the required data, and "collapse" the signal using the useSignal hook. The result is that your component only renders when the end result changes. Notice that useSignal uses deep equality to detect if something actually changes, which means that for instance two arrays with similar contents are considered equal.

Atoms

For mutable state, you can of course use plain old React useState hook, but with that there's always the issue that wherever you apply it, that component will always render when this state changes. If the state is shared between a hierarchy of components, the useState call needs to be at the top of that hierarchy, causing the whole hierachy to be rendered when state changes. Once again you can start appling useMemo and memo but there's another way.

Atoms.

Atom essentially gives you a way to declare state without you needing to render everything when state changes. And that's because an Atom is also a Signal, meaning that you can use pieces of state where they are actually needed. Essentially the interface for Atom is

interface Atom<T> extends Signal<T> {
  set(newValue: T): void
}

The implementation is simple. You can think of an Atom as a local/flexible Redux store that you can combine with other state stores just like any Signals.

In your React appliaction you can create an atom like

const atom: Atom<number> = useAtom(0);

Then use its value where you need it (not necessarily in the root component):

const [count, setCount] = useAtomState(atom);

Because Atom is also a Signal, you can also select a slice using .map, combine it with other state using combineSignals and so on.

Example code

There's currently just one simple example available.

You can run it by cloning the repo and then

npm install
npm run example first

Hopefully I get to make more.

Should I use it?

Probably not.

Might try tho: npm install signaali. Haven't used it in any real applications :)