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 🙏

© 2024 – Pkg Stats / Ryan Hefner

impact-signal

v0.1.6

Published

Reactive signals for React

Downloads

19

Readme

impact-signal

Install

yarn add impact-signal

Description

A signal is just a way to create an observable value. What makes Impact signals especially powerful is that they also make promises observable and suspendable. With an observable reactive primitive your components will only reconcile based on what signals they actually access.

Learn

signal

Creates a value that can be observed by React. Signals are expected to be treated as immutable values, meaning you always need to assign a new value when changing them.

import { signal, observer } from 'impact-signal'

const message = signal('Hello World')

function SomeComponent() {
    using _ = observer()

    return <h1>{message.value}</h1>
}

Signals has first class support for promises. That means when you add a promise to a signal, the promise becomes a SignalPromise. This is the same promise as you passed in, only it is populated with some additional properties and made observable. These properties are also the same React looks for when using the use hook to suspend a promise.

import { signal, observer } from 'impact-signal'

const helloWorldPromise = new Promise<string>((resolve) => {
    setTimeout(() => resolve('Hello World!'), 2000)
})

const message = signal(helloWorldPromise)

function SomeComponent() {
    using _ = observer()

    const promisedMessage = message.value

    if (promisedMessage.status === 'pending') {
        return <div>Loading message...</div>
    }

    if (promisedMessage.status === 'rejected') {
        return <div>Error: {promisedMessage.reason}</div>
    }


    return <h1>{promisedMessage.value}</h1>
})

Or you could suspend it:

import { signal, observer, use } from 'impact-signal'

const helloWorldPromise = new Promise<string>((resolve) => {
    setTimeout(() => resolve('Hello World!'), 2000)
})

const message = signal(helloWorldPromise)

function SomeComponent() {
    using _ = observer()

    const messageValue = use(message.value)

    return <h1>{messageValue}</h1>
})

derived

Creates a signal that lazily recomputes whenever any accessed signals within the derived callback changes. Also signals with promises are supported here.

import { signal, derived } from 'impact-signal'

const message = signal('Hello World')
const shoutingMessage = derived(() => message.value + '!!!')

effect

It will run whenever the signals accessed changes.

import { signal, effect } from 'impact-signal'

const message = signal('Hello World')

const dispose = effect(() => {
    console.log(message.value)
})

observer

To observe signals, and "rerender" the components, they need to bound to an ObserverContext. There are two ways you can achieve this. The traditional way to approach this is using an observer higher order component.

import { observer, signal } from 'impact-app'

const message = signal('Hello World')

const HelloWorld = observer(() => {
    return <h1>{message.value}</h1>
})

But the approach above can result in anonymous component names and dictates to some extent how you can define and export components. Impact signals improves this using a new language feature called explicit resource management. This is in its last phase and ready to be shipped with JavaScript, and already available in TypeScript.

import { observer, signal } from 'impact-signal'

const message = signal('Hello World')

export function HelloWorld() {
    using _ = observer()    

    return <div>{message.value}</div>
}

TypeScript 5.2

Babel

yarn add @babel/plugin-proposal-explicit-resource-management -D
{
    "plugins": [
        "@babel/plugin-proposal-explicit-resource-management"
    ]
}

use

React is experimenting with a new hook called use and until it becomes official you can use the one from Impact to suspend your signal promises.

import { observer } from 'impact-app'
import { useGlobalContext } from '../useGlobalContext'

const DataComponent = observer(() => {
    const { api } = useGlobalContext()
    const data = use(api.fetchData())

    return <div>{data}</div>
})

Queries And Mutations

One of the most common things you do in any web application is to fetch data from the server and change data on the server. Under the hood this is based on promises, but React is not well suited for consuming promises. A suggested new use hook allows you to consume promises directly in components in combination with suspense and error boundaries. This is great, but there is more to data fetching than consuming a promise in a component.

There are several data fetching solutions for React, like useQuery and useSWR, but these are tied to React and its reconciliation loop. That means you are forced to combine your data with Reacts state primitives and the reconciliation loop. They also have strong opinions about caching, refetching mechanisms etc.

Impact signals is a powerful primitive that makes promises observable and suspendable. That makes them a very good candidate for data fetching and mutations.

import { signal } from 'impact-signal'

const posts: Record<string, Signal<Promise<PostDTO>>> = {}

export function fetchPost(id: string) {
    let post = posts[id]

    if (!postQuery) {
        post = posts[id] = signal(
            fetch('/posts/' + id).then((response) => response.json())
        )
    }

    return post.value
}

When a signal receives a promise it will enhance it with status details. Whenever the promise status details update, so does the signal. That means you can observe data fetching and other asynchronous processes. Additionally the status details added to the promise allows you to suspend the promise using the use hook. The React use hook is not available yet, so you can use the one from Impact.

import { observer, use } from 'impact-signal'
import { fetchPost } from '../posts'

const Post = ({ id }: { id: string }) => {
    using _ = observer()

    const post = use(fetchPost(id))
}

But maybe you do not want to use suspense, you just want to deal with the status of the promise directly in the component:

import { observer } from 'impact-signal'
import { fetchPost } from '../posts'

const Post = ({ id }: { id: string }) => {
    using _ = observer()

    const postPromise = fetchPost(id)

    if (postPromise.status === 'pending') {
        return <div>Loading...</div>
    }

    if (postPromise.status === 'rejected') {
        return <div>Some error: {postPromise.reason}</div>
    }

    const post = postPromise.value

    return <div>{post.title}</div>
}

But data fetching is not only about getting and displaying data, it is also about mutations. We can use a promise signal to track the state of doing mutations.

import { signal } from 'impact-signal'

export const changingTitle = signal<Promise<void>>()

function changeTitle(id: string, newTitle: string) {
    // Assign the promise and any consuming components will update
    changingTitle.value = fetch({
        method: 'PUT',
        url: '/posts/' + id,
        data: {
            title: newTitle
        }
    })

    return changingTitle.value
}
import { observer } from 'impact-signal'
import { changeTitle, changingTitle } from '../posts'

function ProjectTitle({ title }: { title: string }) {
    using _ = observer()

    const [newTitle, setNewTitle] = useState(title)

    return (
        <div>
            <input
                disabled={changingTitle?.status === 'pending' ?? false}
                value={newTitle}
                onChange={(event) => setNewTitle(event.target.value)}
                onKeyDown={(event) => {
                    if (event.key === 'ENTER') {
                        changeTitle(id, newTitle)
                    }
                }}
            />
        </div>
    )
}