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

@typectx/react

v0.4.3

Published

Hooks to synchronize React client components with typectx

Downloads

976

Readme

@typectx/react

An adapter to use typectx with React client components. This library provides hooks to integrate typectx's dependency injection into React applications, offering efficient updates and referential integrity.

The purpose of this library is mainly as a proof-of-concept that dependency injection can be integrated with React. It does not aim to become as stable as React Context yet, but can achieve all that React Context does as efficiently as it does.

Features

  • Dependency Injection for React: Define components as typectx products and inject dependencies (resources or other components).
  • Referential Integrity: Components assembled via useAssembleComponent maintain referential equality across re-renders, preventing unnecessary React tree updates.
  • Efficient Updates: Uses useSyncExternalStore and an internal event store to propagate updates only to components that actually depend on changed resources, similar to how React Context works.

Installation

npm install typectx @typectx/react

API

useInit$

Initializes a connection between a React component or hook with @typectx/react's internal store. Components registered will receive updated supplies when a parent component calls the useAssembleComponent hook. Think of this hook as equivalent to useContext, but giving you access to the whole $ object.

function MyComponentFactory(init$, $$) {
    return function MyComponent() {
        // Initialize the scope
        const $ = useInit$(init$);

        // Access dependencies
        const myResource = $(resourceSupplier).unpack();

        return <div>{myResource}</div>;
    }
}

useAssembleComponent (alias useAssembleHook)

Assembles a child component (or hook) with specific resources. This hook ensures that the assembled component reference (and all its transitive component or hook dependencies) remains stable, even if the supplied resources change (updates are handled internally via the store). So React always renders the same component, but the $ state of that component updates in a reactive way, preserving all React's optimizations, and preserving other pieces of state, across rerenders.

const AssembledChild = useAssembleComponent(
    // The supplier to assemble
    $$($$ChildComponent).hire($$Dependency),
    // The resources to supply
    index(
        resourceSupplier.pack(value)
    )
);

const Child = AssembledChild.unpack();
return <Child />;

Usage Example

1. Define Resources (Context)

Define your resources using market.offer().asResource().

// context.ts
import { market } from "typectx"

export const ctx = {
    $$theme: market.offer("theme").asResource<"light" | "dark">(),
    $$user: market.offer("user").asResource<{ name: string } | null>()
}

2. Define Components

Define your components as products using market.offer().asProduct().

// components.ts
import { market, index } from "typectx";
import { useInit$, useAssembleComponent } from "@typectx/react";
import { ctx } from "./context";

// A child component that consumes 'theme'
export const $$Button = market.offer("Button").asProduct({
    suppliers: [ctx.$$theme],
    // Name the function component so you can understand your component tree in React DevTools
    factory: (init$) => function Button({ children }) {
        const $ = useInit$(init$);
        const theme = $(ctx.$$theme).unpack();

        return (
            <button style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
                {children}
            </button>
        );
    }
});

// A parent component that manages state and assembles the child
export const $$App = market.offer("App").asProduct({
    optionals: [ctx.$$theme]
    assemblers: [$$Button],
    factory: (init$, $$) => function App() {
        const $ = useInit$(init$);

        // Assemble the Button component with the current theme
        const $Button = useAssembleComponent(
            $$($$Button),
            index(
                $$(ctx.$$theme).pack("dark") // Supplying 'dark' theme
            )
        );

        const Button = $Button.unpack();

        return (
            <div>
                <h1>My App</h1>
                <Button>Click me</Button>
            </div>
        );
    }
});

3. Root Render

Assemble the root component and render it.

// main.tsx
import { createRoot } from "react-dom/client";
import { index } from "typectx";
import { $$App } from "./components";

const root = createRoot(document.getElementById("root"));

// Assemble the root App
const App = $$App.assemble(index(/* root dependencies if any */)).unpack();

root.render(<App />);

How it Works

  1. Store & Subscription: The library maintains a WeakMap-based store that links their initial state object (init$) to their current state ($). useInit$subscribes the React component to this store usinguseSyncExternalStore.

  2. Stable Assembly: useAssembleComponent creates the component only once to preserve its referential stability. When supplied resources change, it checks if the assembled component or its transitive component dependencies depend directly on the changed resources. The rerendering listeners of only those components get triggered.

Usage tips

  • Rules of Hooks - Don't call hooks in the factory's function body! If you call hooks, like useInit$() or useAssembleComponent() either in a component or custom hooks, be sure to call it in a function returned by the factory, like so:
{
    factory: ($, $$) => () => {
        // useXYZ() or $($$useXYZ).unpack()() if hook part of supplies
    }
}
  • React Context alternative - All you can achieve with React Context can be achieved using @typectx/react's API:

    • createContext() → equivalent to defining a new resource with asResource()
    • useContext() → equivalent to useInit$() then accessing a supply with $(someContextResourceSupplier).unpack()
    • <Provider > → equivalent to useAssemble() with a new value for the supplied resources.

    For a full showcase of this, head over to the example, which displays complex context propagation in a deeply nested component tree. See Assemblers for full documentation.

  • Preload pattern - All factories are eagerly prerun in parallel by default, so preloading is very easy. To preload data, look at file src/api.ts in the demo to see how data prefetching has been achieved with react-query to avoid waterfall loading. The following example shows how to use the preload pattern with @typectx/react.

market.offer("Component").asProduct({
    factory: ($, $$) =>
        React.memo((props) => {
            // return jsx
        }),
    // Init is always called right after the factory, no matter if lazy is true or false
    init: (Component, $) => {
        <Component props={propSetToPreload1} />
        <Component props={propSetToPreload2} />
        <Component props={propSetToPreload3} />
    }
    lazy: false // false is the default, so can be omitted
})

To see how to preload data with Tanstack Query and @typectx/react, head over to the example's api.ts file

  • eslint/rules-of-hooks - You can return anything from the factory, even hooks. If you create hooks as products, you'd call them like $(useXYZ).unpack()() which ESLint can't detect for the rules-of-hooks rule. You could store the hook in a temporary variable instead:
{
    factory: ($, $$) => () => {
        const useXYZ = $(useXYZ).unpack()
        useXYZ() // ESLint rules-of-hooks should work
    }
}
  • eslint-plugin-react-hooks/stable-components - If you use the React official eslint plugin, you may receive complaints from the stable-components rule. useAssembleComponent() returns a component dynamically within another component, which the React Compiler flags as dangerous because every render, the component might have a different Object.is identity, which React uses to optimize renders and associate state. But useAssembleComponent() preserves the referential stability of the returned component across renders, so you can safely ignore this error.