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

@supashiphq/react-sdk

v0.7.12

Published

Supaship SDK for React

Readme

Supaship React SDK

A React SDK for Supaship that provides hooks and components for feature flag management with full TypeScript type safety.

Installation

npm install @supashiphq/react-sdk
# or
yarn add @supashiphq/react-sdk
# or
pnpm add @supashiphq/react-sdk

Quick Start

import { SupaProvider, useFeature, FeaturesWithFallbacks } from '@supashiphq/react-sdk'

// Define your features with type safety
const features = {
  'new-header': false,
  'theme-config': { mode: 'dark' as const, showLogo: true },
  'beta-features': [] as string[],
} satisfies FeaturesWithFallbacks

function App() {
  return (
    <SupaProvider
      config={{
        apiKey: 'your-api-key',
        environment: 'production',
        features,
        context: {
          userID: '123',
          email: '[email protected]',
        },
      }}
    >
      <YourApp />
    </SupaProvider>
  )
}

function YourApp() {
  // Hook returns { feature, isLoading, error, ... }
  const { feature: newHeader, isLoading } = useFeature('new-header')

  if (isLoading) return <div>Loading...</div>

  return <div>{newHeader ? <NewHeader /> : <OldHeader />}</div>
}

Type-Safe Feature Flags

For full TypeScript type safety, define your features and augment the Features interface:

// lib/features.ts
import { FeaturesWithFallbacks, InferFeatures } from '@supashiphq/react-sdk'

export const FEATURE_FLAGS = {
  'new-header': false,
  'theme-config': {
    mode: 'dark' as const,
    primaryColor: '#007bff',
    showLogo: true,
  },
  'beta-features': [] as string[],
  'disabled-feature': null,
} satisfies FeaturesWithFallbacks

// Type augmentation for global type safety, it is required
declare module '@supashiphq/react-sdk' {
  interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}

Now useFeature and useFeatures will have full type safety:

function MyComponent() {
  // TypeScript knows 'new-header' is valid and feature is boolean
  const { feature } = useFeature('new-header')

  // TypeScript knows 'theme-config' returns the exact object shape
  const { feature: config } = useFeature('theme-config')
  // config is { mode: 'dark' | 'light', primaryColor: string, showLogo: boolean }

  // TypeScript will error on invalid feature names
  const { feature: invalid } = useFeature('non-existent-feature') // ❌ Type error
}

See detailed type-safe usage guide

API Reference

SupaProvider

The provider component that makes feature flags available to your React component tree.

<SupaProvider config={config}>{children}</SupaProvider>

Props:

| Prop | Type | Required | Description | | ---------- | ------------------ | -------- | ---------------------------- | | config | SupaClientConfig | Yes | Configuration for the client | | children | React.ReactNode | Yes | Child components | | plugins | SupaPlugin[] | No | Custom plugins | | toolbar | ToolbarConfig | No | Development toolbar settings |

Configuration Options:

import { createFeatures } from '@supashiphq/react-sdk'

const config = {
  apiKey: 'your-api-key',
  environment: 'production',
  features: createFeatures({
    // Required: define all feature flags with fallback values
    'my-feature': false,
    config: { theme: 'light' },
  }),
  context: {
    // Optional: targeting context
    userID: 'user-123',
    email: '[email protected]',
    plan: 'premium',
  },
  networkConfig: {
    // Optional: network settings
    featuresAPIUrl: 'https://api.supashiphq.com/features',
    retry: {
      enabled: true,
      maxAttempts: 3,
      backoff: 1000,
    },
    requestTimeoutMs: 5000,
  },
}

Supported Feature Value Types:

| Type | Example | Description | | --------- | ----------------------------------- | ------------------------- | | boolean | false | Simple on/off flags | | object | { theme: 'dark', showLogo: true } | Configuration objects | | array | ['feature-a', 'feature-b'] | Lists of values | | null | null | Disabled/unavailable flag |

Note: Strings and numbers are not supported as standalone feature values. Use objects instead: { value: 'string' } or { value: 42 }.

useFeature Hook

Retrieves a single feature flag value with React state management and full TypeScript type safety.

const result = useFeature(featureName, options?)

Parameters:

  • featureName: string - The feature flag key
  • options?: object
    • context?: Record<string, unknown> - Context override for this request
    • shouldFetch?: boolean - Whether to fetch the feature (default: true)

Return Value:

{
  feature: T,               // The feature value (typed based on your Features interface)
  isLoading: boolean,       // Loading state
  isSuccess: boolean,       // Success state
  isError: boolean,         // Error state
  error: Error | null,      // Error object if failed
  status: 'idle' | 'loading' | 'success' | 'error',
  refetch: () => void,      // Function to manually refetch
  // ... other query state properties
}

Examples:

function MyComponent() {
  // Simple boolean feature
  const { feature: isEnabled, isLoading } = useFeature('new-ui')

  if (isLoading) return <Skeleton />

  return <div>{isEnabled ? <NewUI /> : <OldUI />}</div>
}

function ConfigComponent() {
  // Object feature
  const { feature: config } = useFeature('theme-config')

  if (!config) return null

  return (
    <div className={config.theme}>
      {config.showLogo && <Logo />}
      <div style={{ color: config.primaryColor }}>Content</div>
    </div>
  )
}

function ConditionalFetch() {
  const { user, isLoading: userLoading } = useUser()

  // Only fetch when user is loaded
  const { feature } = useFeature('user-specific-feature', {
    context: { userId: user?.id },
    shouldFetch: !userLoading && !!user,
  })

  return <div>{feature && <SpecialContent />}</div>
}

useFeatures Hook

Retrieves multiple feature flags in a single request with type safety.

const result = useFeatures(featureNames, options?)

Parameters:

  • featureNames: readonly string[] - Array of feature flag keys
  • options?: object
    • context?: Record<string, unknown> - Context override for this request
    • shouldFetch?: boolean - Whether to fetch features (default: true)

Return Value:

{
  features: { [key: string]: T },  // Object with feature values (typed based on keys)
  isLoading: boolean,
  isSuccess: boolean,
  isError: boolean,
  error: Error | null,
  status: 'idle' | 'loading' | 'success' | 'error',
  refetch: () => void,
  // ... other query state properties
}

Examples:

function Dashboard() {
  const { user } = useUser()

  // Fetch multiple features at once (more efficient than multiple useFeature calls)
  const { features, isLoading } = useFeatures(['new-dashboard', 'beta-mode', 'show-sidebar'], {
    context: {
      userId: user?.id,
      plan: user?.plan,
    },
  })

  if (isLoading) return <LoadingSpinner />

  return (
    <div className={features['new-dashboard'] ? 'new-layout' : 'old-layout'}>
      {features['show-sidebar'] && <Sidebar />}
      {features['beta-mode'] && <BetaBadge />}
      <MainContent />
    </div>
  )
}

function FeatureList() {
  // TypeScript will infer the correct types for each feature
  const { features } = useFeatures(['feature-a', 'feature-b', 'config-feature'])

  return (
    <div>
      {features['feature-a'] && <FeatureA />}
      {features['feature-b'] && <FeatureB />}
      {features['config-feature'] && <ConfigDisplay config={features['config-feature']} />}
    </div>
  )
}

SupaFeature Component

A declarative component for rendering different UI based on boolean feature flag values.

<SupaFeature
  feature="feature-name"
  loading={<Skeleton />}
  variations={{
    true: <ComponentA />,
    false: <ComponentB />,
  }}
/>

Props:

| Prop | Type | Required | Description | | ------------- | --------------------------------------- | -------- | ---------------------------------------------------------------- | | feature | string | Yes | The feature flag key to evaluate | | variations | { true: ReactNode, false: ReactNode } | Yes | Components to render for true/false values | | loading | ReactNode | No | Component to render while loading | | fallback | ReactNode | No | Component to render when feature value is neither true nor false | | context | Record<string, unknown> | No | Context override for this feature | | shouldFetch | boolean | No | Whether to fetch the feature (default: true) |

Examples:

// Simple boolean feature flag
function Header() {
  return (
    <SupaFeature
      feature="new-header"
      loading={<HeaderSkeleton />}
      variations={{
        true: <NewHeader />,
        false: <OldHeader />,
      }}
    />
  )
}

// With context
function UserDashboard() {
  const { user } = useAuth()

  return (
    <SupaFeature
      feature="beta-dashboard"
      context={{ userId: user.id, plan: user.plan }}
      variations={{
        true: <BetaDashboard />,
        false: <StandardDashboard />,
      }}
    />
  )
}

useFeatureContext Hook

Access and update the feature context within components.

const { context, updateContext } = useFeatureContext()

Example:

function UserProfileSettings() {
  const { context, updateContext } = useFeatureContext()
  const [user, setUser] = useState(null)

  const handleUserUpdate = newUser => {
    setUser(newUser)

    // Update feature context when user changes
    // This will trigger refetch of all features
    updateContext({
      userId: newUser.id,
      plan: newUser.subscriptionPlan,
      segment: newUser.segment,
    })
  }

  return <form onSubmit={handleUserUpdate}>{/* User profile form */}</form>
}

useClient Hook

Access the underlying SupaClient instance for advanced use cases.

const client = useClient()

// Use client methods directly
const feature = await client.getFeature('my-feature', { context: { ... } })
const features = await client.getFeatures(['feature-1', 'feature-2'])

Best Practices

1. Always Use satisfies for Feature Definitions

// ✅ Good - preserves literal types
const features = {
  'dark-mode': false,
  theme: { mode: 'light' as const, variant: 'compact' as const },
} satisfies FeaturesWithFallbacks

// ❌ Bad - loses literal types (don't use type annotation)
const features: FeaturesWithFallbacks = {
  'dark-mode': false,
  theme: { mode: 'light', variant: 'compact' }, // Types widened to string
}

2. Centralize Feature Definitions

// ✅ Good - centralized feature definitions
// lib/features.ts
export const FEATURE_FLAGS = {
  'new-header': false,
  theme: { mode: 'light' as const },
  'beta-features': [] as string[],
} satisfies FeaturesWithFallbacks

// ❌ Bad - scattered feature definitions
const config1 = { features: { 'feature-1': false } satisfies FeaturesWithFallbacks }
const config2 = { features: { 'feature-2': true } satisfies FeaturesWithFallbacks }

3. Use Type Augmentation for Type Safety

// ✅ Good - type augmentation for global type safety
declare module '@supashiphq/react-sdk' {
  interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}

// Now all useFeature calls are type-safe
const { feature } = useFeature('new-header') // ✅ TypeScript knows this is boolean
const { feature } = useFeature('invalid') // ❌ TypeScript error

3. Use Context for User Targeting

function App() {
  const { user } = useAuth()

  return (
    <SupaProvider
      config={{
        apiKey: 'your-api-key',
        features: FEATURE_FLAGS,
        context: {
          userId: user?.id,
          email: user?.email,
          plan: user?.subscriptionPlan,
          version: process.env.REACT_APP_VERSION,
        },
      }}
    >
      <YourApp />
    </SupaProvider>
  )
}

4. Batch Feature Requests

// ✅ Good - single API call
const { features } = useFeatures(['feature-1', 'feature-2', 'feature-3'])

// ❌ Less efficient - multiple API calls
const feature1 = useFeature('feature-1')
const feature2 = useFeature('feature-2')
const feature3 = useFeature('feature-3')

5. Handle Loading States

function MyComponent() {
  const { user, isLoading: userLoading } = useUser()

  const { features, isLoading: featuresLoading } = useFeatures(['user-specific-feature'], {
    context: { userId: user?.id },
    shouldFetch: !userLoading && !!user,
  })

  if (userLoading || featuresLoading) return <Skeleton />

  return <div>{features['user-specific-feature'] && <SpecialContent />}</div>
}

6. Update Context Reactively

function UserDashboard() {
  const { updateContext } = useFeatureContext()
  const [currentPage, setCurrentPage] = useState('dashboard')

  // Update context when navigation changes
  useEffect(() => {
    updateContext({ currentPage })
  }, [currentPage, updateContext])

  return (
    <div>
      <Navigation onPageChange={setCurrentPage} />
      <PageContent page={currentPage} />
    </div>
  )
}

Framework Integration

Next.js App Router (Next.js 13+)

// app/providers.tsx
'use client'
import { SupaProvider, FeaturesWithFallbacks } from '@supashiphq/react-sdk'

const FEATURE_FLAGS = {
  'new-hero': false,
  theme: { mode: 'light' as const },
} satisfies FeaturesWithFallbacks

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <SupaProvider
      config={{
        apiKey: process.env.NEXT_PUBLIC_SUPASHIP_API_KEY!,
        environment: process.env.NODE_ENV!,
        features: FEATURE_FLAGS,
      }}
    >
      {children}
    </SupaProvider>
  )
}

// app/layout.tsx
import { Providers } from './providers'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

// app/page.tsx
;('use client')
import { useFeature } from '@supashiphq/react-sdk'

export default function HomePage() {
  const { feature: newHero } = useFeature('new-hero')

  return <main>{newHero ? <NewHeroSection /> : <OldHeroSection />}</main>
}

Next.js Pages Router (Next.js 12 and below)

// pages/_app.tsx
import { SupaProvider, FeaturesWithFallbacks } from '@supashiphq/react-sdk'
import type { AppProps } from 'next/app'

const FEATURE_FLAGS = {
  'new-homepage': false,
} satisfies FeaturesWithFallbacks

export default function App({ Component, pageProps }: AppProps) {
  return (
    <SupaProvider
      config={{
        apiKey: process.env.NEXT_PUBLIC_SUPASHIP_API_KEY!,
        environment: process.env.NODE_ENV!,
        features: FEATURE_FLAGS,
      }}
    >
      <Component {...pageProps} />
    </SupaProvider>
  )
}

Vite / Create React App

// src/main.tsx or src/index.tsx
import { SupaProvider, FeaturesWithFallbacks } from '@supashiphq/react-sdk'

const FEATURE_FLAGS = {
  'new-ui': false,
  theme: { mode: 'light' as const },
} satisfies FeaturesWithFallbacks

function App() {
  return (
    <SupaProvider
      config={{
        apiKey: import.meta.env.VITE_SUPASHIP_API_KEY, // Vite
        // or
        apiKey: process.env.REACT_APP_SUPASHIP_API_KEY, // CRA
        environment: import.meta.env.MODE,
        features: FEATURE_FLAGS,
      }}
    >
      <YourApp />
    </SupaProvider>
  )
}

Development Toolbar

The SDK includes a development toolbar for testing and debugging feature flags locally.

<SupaProvider
  config={{ ... }}
  toolbar={{
    enabled: 'auto', // 'auto' | 'always' | 'never'
    position: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
  }}
>
  <YourApp />
</SupaProvider>
  • 'auto': Shows toolbar in development environments only (default)
  • true: Always shows toolbar
  • false: Never shows toolbar

The toolbar allows you to:

  • View all available feature flags
  • Override feature values locally
  • See feature value types and current values
  • Clear local overrides

Testing

Mocking Feature Flags in Tests

// test-utils/providers.tsx
import { SupaProvider, FeaturesWithFallbacks } from '@supashiphq/react-sdk'

export function TestProviders({ children, features = {} as FeaturesWithFallbacks }) {
  return (
    <SupaProvider
      config={{
        apiKey: 'test-key',
        environment: 'test',
        features,
        context: {},
      }}
    >
      {children}
    </SupaProvider>
  )
}

Example Test

// MyComponent.test.tsx
import { render, screen } from '@testing-library/react'
import { TestProviders } from '../test-utils/providers'
import MyComponent from './MyComponent'

describe('MyComponent', () => {
  it('shows new feature when enabled', () => {
    render(
      <TestProviders features={{ 'new-feature': true }}>
        <MyComponent />
      </TestProviders>
    )

    expect(screen.getByText('New Feature Content')).toBeInTheDocument()
  })

  it('shows old feature when disabled', () => {
    render(
      <TestProviders features={{ 'new-feature': false }}>
        <MyComponent />
      </TestProviders>
    )

    expect(screen.getByText('Old Feature Content')).toBeInTheDocument()
  })
})

Troubleshooting

Common Issues

Type errors with FeaturesWithFallbacks

If you encounter type errors when defining features, ensure you're using the correct pattern:

Solution: Always use satisfies FeaturesWithFallbacks (not type annotation)

// ✅ Good - preserves literal types
const features = {
  'my-feature': false,
  config: { theme: 'dark' as const },
} satisfies FeaturesWithFallbacks

// ❌ Bad - loses literal types
const features: FeaturesWithFallbacks = {
  'my-feature': false,
  config: { theme: 'dark' }, // Widened to string
}

Provider Not Found Error

Error: useFeature must be used within a SupaProvider

Solution: Ensure your component is wrapped in a SupaProvider:

// ✅ Correct
function App() {
  return (
    <SupaProvider config={{ ... }}>
      <MyComponent />
    </SupaProvider>
  )
}

// ❌ Incorrect
function App() {
  return <MyComponent /> // Missing provider
}

Features Not Loading

  • Check API key: Verify your API key is correct
  • Check network: Open browser dev tools and check network requests
  • Check features config: Ensure features are defined in the config

Type Errors

Property 'my-feature' does not exist on type 'Features'

Solution: Add type augmentation:

import { InferFeatures } from '@supashiphq/react-sdk'
import { FEATURE_FLAGS } from './features'

declare module '@supashiphq/react-sdk' {
  interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}

License

This project is licensed under the MIT License - see the LICENSE file for details.