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

@formations-embedded/react

v0.1.1

Published

React component library for embedding Formations S-Corp workflows into partner apps.

Readme

@formations-embedded/react

React component library partners install to embed Formations S-Corp workflows directly inside their own product.

This package is currently 0.1.0 — pre-publish. Public API is liable to change until we tag 1.0.0.

Installation

npm install @formations-embedded/react react react-dom

Material UI, Emotion, and React Query are bundled inside the package. The library only declares react and react-dom (>=17) as peer dependencies.

Usage

import { Dashboard, Formations } from "@formations-embedded/react";
import "@formations-embedded/react/index.css";

function HomeDashboard({ accessToken }: { accessToken: string }) {
  return (
    <Formations environment="SANDBOX" accessToken={accessToken}>
      <Dashboard />
    </Formations>
  );
}

That's the minimum needed to render an embedded dashboard. <Formations> wraps children in the shared Formations MUI theme automatically — you do not need a separate ThemeProvider unless you want to customize it (see Material UI theme).

see Token lifecycle below.

Material UI theme

Embedded components use Material UI with the same Formations design tokens as the main app. <Formations> applies FormationsThemeProvider (ThemeProvider + StyledEngineProvider injectFirst) automatically.

To reuse the theme in tests or Storybook:

import {
  FormationsThemeProvider,
  theme,
  FormationsPrimaryButton,
} from "@formations-embedded/react";
import { MockTheme } from "@formations-embedded/react/testing";

render(
  <MockTheme>
    <Dashboard />
  </MockTheme>,
);

Export theme, palette, typography, and button helpers when building custom embed UI that should match Formations styling.

Icons (FormationsIcon) live in a separate entry so the default embed bundle stays small — import only when needed:

import { FormationsIcon } from "@formations-embedded/react/icon";

<Formations> props

| Prop | Type | Required | Description | | --------------------- | ----------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------- | | environment | "SANDBOX" \| "PRODUCTION" | yes | Which Formations backend the API client targets. | | accessToken | string | yes | A customer-scoped JWT minted by your backend. | | refreshAccessToken | (ctx) => Promise<string> | no | Called when the token is about to expire or after a 401. Resolve with a fresh token to keep the session alive. | | refreshLeadTimeMs | number | no | How early (ms) before exp to refresh proactively. Defaults to 60_000. | | apiBaseUrlOverride | string | no | Point the client at a non-default base URL (useful when developing against a local Formations backend). | | onError | (error: FormationsError) => void | no | Notified for unrecoverable transport / auth errors. | | queryClient | QueryClient | no | React Query client instance. Defaults to the package singleton. Import QueryClient from this package if you need a shared client. |

React Query

React Query v3 is bundled — you do not install it separately. Import hooks and types from @formations-embedded/react:

import { useQuery, useQueryClient, queryClient, type UseQueryOptions } from "@formations-embedded/react";

<Formations> wraps children in a QueryClientProvider using the shared queryClient singleton (same defaults as the main Formations app: 5-minute staleTime / cacheTime, no retries, no refetch on window focus).

Data fetching follows the same three-layer pattern as the main codebase:

Component  →  hooks/api/*  →  services/*

Query hooks

import { useMe, useDashboard } from "@formations-embedded/react";

function Profile() {
  const { me, isLoading, error } = useMe();
  const { dashboard, refetch } = useDashboard();
  // ...
}

Adding new hooks

  1. Add a service function in services/ that accepts ApiClient.
  2. Create hooks/api/useXxx.ts wrapping useQuery / useMutation.
  3. Export from the package index.ts when it's part of the public API.
// hooks/api/useWidgets.ts (template)
import {
  useQuery,
  useQueryClient,
  useMutation,
  type UseQueryOptions,
  useFormations,
} from "@formations-embedded/react";
import { getWidgets, createWidget } from "../../services/widgets";

export const useWidgets = (options?: UseQueryOptions<Widget[]>) => {
  const { api, accessToken } = useFormations();
  const { data, ...rest } = useQuery<Widget[]>(
    ["widgets", accessToken],
    () => getWidgets(api),
    { enabled: !!accessToken, ...options },
  );
  return { widgets: data ?? [], ...rest };
};

Cache management

Import the singleton when you need cache operations outside React:

import { queryClient } from "@formations-embedded/react";

queryClient.invalidateQueries(["dashboard"]);

Inside hooks, prefer useQueryClient() from @formations-embedded/react.

Sharing a query client

<Formations> wraps children in its own QueryClientProvider by default. Pass queryClient to share a single client across multiple embeds, or create one with the re-exported QueryClient class:

import { Formations, QueryClient } from "@formations-embedded/react";

const sharedClient = new QueryClient();

<Formations queryClient={sharedClient} ...>

Testing

import {
  createQueryWrapper,
  mockedQueryClient,
} from "@formations-embedded/react/testing";

beforeEach(async () => {
  await mockedQueryClient.resetQueries();
});

const { result } = renderHook(() => useMe(), {
  wrapper: createQueryWrapper(),
});

Token lifecycle

  1. Your backend mints a short-lived (≤ 60 min) customer-scoped JWT by calling Formations' partner endpoints — see BACKEND_API.md section 1.
  2. You pass that JWT in as accessToken. The library decodes the exp claim locally to schedule proactive refresh.
  3. refreshLeadTimeMs before exp, the library invokes your refreshAccessToken callback. Your callback typically fetches a new token from your own backend (which performs the S2S exchange).
  4. If a request happens to race the refresh and gets a 401, the library calls refreshAccessToken (sharing the in-flight promise if one is already running) and retries the request exactly once.
  5. If refreshAccessToken rejects or isn't provided, the library fires onError with code: "AUTH_FAILED". The host app is responsible for re-authenticating the user.
<Formations
  environment="SANDBOX"
  accessToken={accessToken}
  refreshAccessToken={async ({ reason }) => {
    // Call YOUR backend, not Formations directly. Your backend then
    // does the S2S exchange with Formations.
    const res = await fetch("/api/formations-token", {
      headers: { "x-refresh-reason": reason },
    });
    if (!res.ok) throw new Error(`refresh failed: ${res.status}`);
    const body = await res.json();
    return body.accessToken;
  }}
  onError={(err) => console.error("[Formations]", err)}
>
  <Dashboard />
</Formations>

Why not have the library refresh the token itself?

Refreshing requires partner credentials, and partner credentials must never live in the browser. The library deliberately delegates refresh to your backend so secrets stay on the server.

Components

<Dashboard />

Renders the S-Corp summary view. Loads GET /api/embedded/v1/dashboard on mount and re-renders when the token rotates. See DashboardData for the response shape.

Hooks (advanced)

useMe() / useDashboard()

Typed React Query hooks for the built-in endpoints. Return domain-named data plus standard query fields (isLoading, error, refetch, etc.).

useFormations()

Returns { environment, accessToken, api, tokenManager }. Use api.request<T>(path, opts) to call any Formations endpoint with the current access token (it handles 401 → refresh → retry for you).

import { useFormations, useApiResource } from "@formations-embedded/react";

function MyEmbed() {
  const { api } = useFormations();
  const { data, loading, refetch } = useApiResource<MyShape>("/api/embedded/v1/something");
  // ...
}

useApiResource<T>(path)

React Query-backed generic fetcher for arbitrary paths. Automatically re-runs when the token rotates. Returns { data, error, loading, refetch }. Prefer typed hooks in hooks/api for known endpoints.

Errors

All recoverable failures resolve to a FormationsError:

interface FormationsError {
  code: "AUTH_FAILED" | "TOKEN_EXPIRED" | "NETWORK_ERROR" | "API_ERROR" | "CONFIG_ERROR";
  message: string;
  status?: number;
  cause?: unknown;
}

The onError callback on <Formations> receives these as they happen. Hooks like useApiResource also expose the error via their error field.

Backend dependency

The library expects specific endpoints on the Formations backend. See BACKEND_API.md for the full contract — that file is the source of truth for backend implementation work.

Development

The fastest inner loop is to run the sample app and edit library source directly — Vite aliases @formations-embedded/react to this package's src/ so HMR fires on every save:

# from repo root
npm install
npm run dev      # opens the DevUI playground at http://localhost:5173

For raw build / typecheck:

npm run build --workspace @formations-embedded/react
npm run typecheck --workspace @formations-embedded/react

Bundle size

The production build minifies and tree-shakes dependencies. Approximate sizes after npm run build --workspace @formations-embedded/react:

| Artifact | Minified | Gzipped | | -------- | -------- | ------- | | Main (index.js) | ~230 KB | ~73 KB | | Icons (icon.js + font) | ~4.2 MB | ~1.5 MB | | Testing (testing.js) | ~116 KB | — |

The main bundle includes MUI, Emotion, and React Query. Material Symbols fonts are not loaded unless you import @formations-embedded/react/icon. Test helpers import from @formations-embedded/react/testing.