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

@providame/react

v0.0.15

Published

Providame React bindings — headless hooks and prebuilt auth components.

Readme

@providame/react

React bindings for Providame — headless hooks (build your own UI) and prebuilt components (drop-in), both over the same @providame/core engine.

Building a native or hybrid app (React Native or Expo, or any app without a browser cookie jar)? Use a native key (nk_) instead of your publishable key — native mode comes from @providame/core and is configured on the client. See the Native apps guide.

Install

npm install @providame/react react react-dom

Usage

import { ProvidameProvider, SignIn } from '@providame/react'

function App() {
  return (
    <ProvidameProvider publishableKey="pk_test_...">
      <SignIn onComplete={(session) => console.log(session)} signUpUrl="/sign-up" />
    </ProvidameProvider>
  )
}

Modal vs. page usage

These components serve two contexts, and the footer link behaves differently in each:

  • Page — you render <SignIn> at your own /sign-in route. Pass signUpUrl and the footer renders a normal link that navigates.
  • Modal — <SignInButton mode="modal"> handles this for you: the footer switches to sign-up in place, because navigating would tear the modal down.

Rendering these inside your own modal? Pass the switch callback instead of a URL, and hold the view state yourself:

{view === "sign-in" ? (
  <SignIn onSwitchToSignUp={() => setView("sign-up")} />
) : (
  <SignUp onSwitchToSignIn={() => setView("sign-in")} />
)}

The callback takes precedence when both it and a URL are supplied.

SignInButton no longer accepts signUpUrl (nor SignUpButton signInUrl). They had no effect in mode="redirect" and produced a modal-destroying navigation in mode="modal". Pass those URLs to <SignIn>/<SignUp> directly when rendering them as pages.

Headless

import { useSignIn } from '@providame/react'

function CustomSignIn() {
  const signIn = useSignIn()
  const submit = () => signIn.signInWithPassword('[email protected]', 'password')
  return <button onClick={submit}>Sign in</button>
}

Beyond create()/attemptFirstFactor()/signInWithPassword(), useSignIn() also exposes one call per passwordless starter, plus the second-factor and forced-enrollment steps a password sign-in can land on too:

await signIn.signInWithPasskey(identifier, window.location.hostname)

await signIn.signInWithOTPEmail(identifier)
await signIn.resendOTPEmail()
await signIn.attemptFirstFactor({ strategy: 'otp_email', code })

await signIn.signInWithOTPSMS(identifier)   // identifier is still email; the code texts to the phone on file
await signIn.resendOTPSMS()
await signIn.attemptFirstFactor({ strategy: 'otp_sms', code })

await signIn.signInWithMagicLink(identifier, 'https://myapp.com/auth/callback')

// Second factor / forced enrollment
await signIn.attemptSecondFactor({ strategy: 'totp', code })
await signIn.enrollTotp()                // status "needs_mfa_enrollment" -> signIn.enrollment.{ secret, otpauthUri }
await signIn.verifyTotpEnrollment(code)

signIn.firstFactors   // FactorKind[] the backend will accept next
signIn.secondFactors  // FactorKind[]
signIn.recoveryCodes  // string[] | null, populated once right after a mid-flow TOTP enrollment

Component styles are injected automatically at runtime — no separate CSS import is needed. (@providame/vue is the exception: it ships a real stylesheet you must import.)

Social login

import { useSignInIdp } from '@providame/react'

function GoogleButton() {
  const { signIn } = useSignInIdp()
  return <button onClick={() => signIn('google')}>Sign in with Google</button>
}

useSignInIdp() also exposes providers (this environment's enabled social-login providers, fetched on mount) and error (populated by a failed start request, or by a post-redirect social-login error).

Sign-up

useSignUp() is symmetric to useSignIn(), including the same mid-flow TOTP enrollment step — <SignUp> is built entirely on top of it:

import { useSignUp } from '@providame/react'

function CustomSignUp() {
  const signUp = useSignUp()

  async function submit() {
    await signUp.create({ email, password, firstName, lastName, inviteToken, turnstileToken })
    // status is now "needs_verification"
  }

  async function verify(code: string) {
    await signUp.attemptVerification({ code })
  }

  // Forced MFA enrollment, once status is "needs_mfa_enrollment"
  async function enroll(code: string) {
    await signUp.enrollTotp()                // -> signUp.enrollment.{ secret, otpauthUri }
    await signUp.verifyTotpEnrollment(code)  // completes the flow
  }
}

inviteToken is required when the environment's sign-up policy is invite_only (read it off your own invite-accept page's URL/props); every other mode ignores it if set. A waitlist-mode environment completes verification but returns no session — check status === "waitlisted" rather than isComplete. needsVerification is a convenience alias for status === "needs_verification".

Password reset

<SignIn>'s own "Forgot password?" link already drives this flow in place. Render the standalone <ResetPassword> component to give it its own page/route instead:

import { ResetPassword } from '@providame/react'

<ResetPassword signInUrl="/sign-in" onComplete={(session) => navigate('/dashboard')} />

Headless, useResetPassword() is symmetric to useSignIn()/useSignUp():

import { useResetPassword } from '@providame/react'

function CustomResetPassword() {
  const reset = useResetPassword()

  await reset.create({ email })            // status -> "needs_verification"
  await reset.resend()
  await reset.attempt({ code, password })  // sets the new password, signs the user in

  // Second factor / forced enrollment — same shape as useSignIn/useSignUp
  await reset.attemptSecondFactor({ strategy: 'totp', code })
  await reset.enrollTotp()
  await reset.verifyTotpEnrollment(code)

  reset.status         // FlowStatus
  reset.session        // Session | null, once complete
  reset.recoveryCodes  // string[] | null
  reset.reset()        // back to "idle"
}

Customizing text and logo

<SignIn>, <SignUp>, and <ResetPassword> all accept title, subtitle, and logo to override the heading, subheading, and logo image — each falls back to a branding-derived value (or a plain default) when omitted. <SignUp> additionally accepts collectName (default true, shows first/last name fields) and inviteToken (see Sign-up above).

<SignIn title="Welcome back" subtitle="Sign in to continue" logo="/logo.svg" />

What's in the box

Components — SignIn, SignUp, ResetPassword, UserButton, UserProfile, SignInButton, SignUpButton, SignOutButton, SignedIn, SignedOut, Protect, RedirectToSignIn, OrganizationSwitcher, OrganizationProfile, CreateOrganization.

Hooks — useAuth, useUser, useSession, useSignIn, useSignUp, useSignInIdp, useResetPassword, useOrganization, useOrganizationList, useBranding, usePasskeyRegistrationLink, useProvidame.

Hook return values are plain snapshots of the render that produced them. Checking flow state synchronously right after an await inside a handler reads the stale value — react to it in a useEffect instead. (This is the one place the React and Vue bindings genuinely differ: Vue's refs are live.)

Buttons & account menu

<SignInButton mode="modal" onComplete={(session) => console.log(session)} />
<SignUpButton mode="modal" onComplete={(session) => console.log(session)} />
<UserButton onSignOut={() => navigate('/')} />

onComplete fires once the flow inside the modal finishes; mode="redirect" navigates to signInUrl/signUpUrl instead and ignores it. <UserButton>'s onSignOut fires after a successful sign-out.

Organizations

import {
  OrganizationSwitcher, OrganizationProfile, CreateOrganization,
  useOrganization, useOrganizationList,
} from '@providame/react'

// Reads token claims — makes no request
const { organization, roles, permissions, has } = useOrganization()

// Fetches GET /me/organizations
const list = useOrganizationList()
list.isLoaded        // false until the first load settles (successfully or not)
list.organizations

<OrganizationSwitcher hideCreate personalLabel="Just me" onChange={(orgId) => console.log(orgId)} />
<OrganizationProfile organizationId={someOtherOrgId} onClose={() => setOpen(false)} />
<CreateOrganization onCreated={(orgId) => list.setActive(orgId)} onCancel={() => setOpen(false)} />

useOrganization() covers the active organization only and never fetches — the token already carries org_id/org_slug/org_roles/org_permissions. useOrganizationList() does fetch, since the full membership list isn't in a token; it also exposes setActive(orgId).

setActive() is remembered across sign-ins, so you don't call it on every login: a returning user is put back in the organization they last used, a user who has never chosen starts in the one they joined first, and clearing it sticks too.

<OrganizationSwitcher>'s hideCreate drops the "Create organization" action; personalLabel/hidePersonal customize or remove the no-organization option.

<OrganizationProfile> manages the active organization by default, or a specific one via organizationId; onClose fires when its "Done" button is clicked. It gates each action on the caller's own org:sys_* permissions rather than a role name, so it works with roles you define yourself.

<CreateOrganization>'s onCreated/onCancel are the only way to react to a successful create or cancel — there's no default navigation.

Gating UI

<Protect role="admin">...</Protect>
<Protect orgPermission="org:sys_memberships:manage">...</Protect>
<Protect role="admin" orgRole="org:admin">...</Protect>   {/* both must pass */}

role/permission gate on environment-level grants; orgRole/orgPermission gate on the active organization. Omitted props are not constraints. This is UI gating only — real enforcement is your backend verifying the token, via @providame/backend.

Object-scoped access

<Protect> reads claims already in the token — synchronous, free, never fails. <Can> asks a question about one specific resource — a network call, so it needs a loading state and can fail:

<Can
  permission="edit"
  resource="document:42"
  context={{ status: document.status }}
  loading={<Spinner />}
  fallback={<p>You can't edit this document.</p>}
>
  <button>Edit</button>
</Can>

Use <Protect role="admin"> for "is this user an admin"; use <Can permission="edit" resource="document:42"> for "can this user edit this document". <Can> fails closed — a network error renders fallback, never children. The optional context prop passes extra data an authorization condition can evaluate against — omit it when the permission doesn't need one. useCan({ permission, resource, context }) is the headless equivalent, returning { allowed, isLoading, error }.

Answers are cached per-question for the life of the session (cleared on sign-in/out and organization switch) — mounting <Can> for the same resource twice on one page costs one request, not two.

Theming

Every component accepts an appearance prop:

<SignIn appearance={{ variables: { colorPrimary: '#111827' } }} />

Components also fetch your environment's configured branding (colors, logo, light and dark) on mount and apply it automatically.

Branding & the shared client

Every prebuilt component already calls useBranding() on mount — reach for it directly only if you're building fully custom UI and want the same colors/logo. The underlying fetch is memoized on the client, so mounting several components on one page still costs a single request. useProvidame() returns the shared client instance itself — the same one every hook above uses internally — for anything not covered by a hook:

import { useBranding, useProvidame } from '@providame/react'

function CustomHeader() {
  const branding = useBranding()   // { logoUrl, primaryColor, appName, ... }
  const providame = useProvidame() // the shared client every hook above uses internally

  return <img src={branding.logoUrl} alt={branding.appName} />
}

Verifying tokens & errors

verifyToken and ProvidameError are re-exported from @providame/core for convenience — the full reference for server-side verification is @providame/backend, which wraps the same function.

// On your own backend — never in the browser.
import { verifyToken } from '@providame/react'

const { userId, claims } = await verifyToken(accessToken, {
  jwksUri: config.jwksUri,
  issuer: config.issuer,
  audience: config.projectId,
})

Every hook's error field above is a ProvidameError: code is the backend's error code, status the HTTP status, and fields a per-field validation map when isValidationError is true. isInvalidCredentials is true for a rejected credential/factor (a 401, or the invalid_credentials code):

if (signIn.error?.isInvalidCredentials) {
  // a rejected credential/factor — a 401, or code "invalid_credentials"
} else if (signIn.error?.isValidationError) {
  signIn.error.fields // per-field validation messages
}

Docs

Full reference: /docs/sdk/react in your Providame dashboard.

Build

npm run build