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

@dismissible/react-client

v3.0.0

Published

<p align="center"> <a href="https://dismissible.io" target="_blank"><img src="https://raw.githubusercontent.com/DismissibleIo/dismissible-api/main/docs/images/dismissible_logo.png" width="240" alt="Dismissible" /></a> </p>

Readme

Dismissible manages the state of your UI elements across sessions, so your users see what matters, once! No more onboarding messages reappearing on every tab, no more notifications haunting users across devices. Dismissible syncs dismissal state everywhere, so every message is intentional, never repetitive.

@dismissible/react-client

This is the React component library for creating dismissible UI elements with persistent state management.

This component is used with the Dismissible API Server, which you can self-host with Docker or integrate into your NestJS application.

dismissible.io | Documentation | API Server

Features

  • Easy to use - Simple component API for dismissible content
  • Persistent state - Dismissal state is saved and restored across sessions when using the Dismissible API Server
  • Automatic request batching - Multiple items requested in the same render cycle are automatically coalesced into a single API call
  • Restore support - Restore previously dismissed items programmatically
  • JWT Authentication - Built-in support for secure JWT-based authentication
  • Custom HTTP Client - Bring your own HTTP client (axios, ky, etc.) with custom headers, interceptors, and tracking
  • Customizable - Custom loading, error, and dismiss button components
  • Accessible - Built with accessibility best practices
  • Hook-based - Includes useDismissibleItem hook for custom implementations
  • Lightweight - Minimal bundle size with tree-shaking support
  • TypeScript - Full TypeScript support with complete type definitions

Installation

npm install @dismissible/react-client

Peer Dependencies

Make sure you have React 18+ installed:

npm install react react-dom

Quick Start

1. Set up the Dismissible API Server

First, you need a Dismissible API Server. The easiest way is with Docker:

# docker-compose.yml
version: '3.8'
services:
  api:
    image: dismissibleio/dismissible-api:latest
    ports:
      - '3001:3001'
    environment:
      DISMISSIBLE_PORT: 3001
docker-compose up -d

OR

docker run -p 3001:3001 -e DISMISSIBLE_PORT=3001 dismissibleio/dismissible-api:latest

See the API Server documentation for more deployment options including NestJS integration, public Docker image and more.

2. Configure the Provider

Wrap your app with DismissibleProvider. The userId prop is required to track all your dismissals per user:

import { DismissibleProvider } from '@dismissible/react-client';

function App() {
  const userId = getCurrentUserId();
  
  return (
    <DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
      <YourApp />
    </DismissibleProvider>
  );
}

3. Use Dismissible Components

Now wrap any component you want to be dismissible with the <Dismissible> component, and the itemId, along with the userId, will become the unique key that is tracked across sessions and devices.

import { Dismissible } from '@dismissible/react-client';

function WelcomeBanner() {
  return (
    <Dismissible itemId="welcome-banner">
      <div className="banner">
        <h2>Welcome to our app!</h2>
        <p>This banner can be dismissed and won't show again.</p>
      </div>
    </Dismissible>
  );
}

API Reference

<DismissibleProvider> Component

Context provider that configures authentication and API settings. Required - all <Dismissible> components must be wrapped in a provider.

Props

| Prop | Type | Required | Description | |------|------|----------|-------------| | userId | string | ✅ | User ID for tracking dismissals per user | | baseUrl | string | ✅ | API base URL for your self-hosted server | | jwt | string \| (() => string) \| (() => Promise<string>) | ❌ | JWT token for secure authentication | | client | DismissibleClient | ❌ | Custom HTTP client implementation (uses default if not provided) | | children | ReactNode | ✅ | Components that will use the dismissible functionality |

Example

import { DismissibleProvider } from '@dismissible/react-client';

// Basic setup with userId
function App() {
  return (
    <DismissibleProvider userId="user-123" baseUrl="http://localhost:3001">
      <YourApp />
    </DismissibleProvider>
  );
}

// With static JWT
function AppWithJWT() {
  return (
    <DismissibleProvider 
      userId="user-123" 
      jwt="eyJhbGciOiJIUzI1NiIs..."
      baseUrl="https://api.yourapp.com"
    >
      <YourApp />
    </DismissibleProvider>
  );
}

// With dynamic JWT function
function AppWithDynamicAuth() {
  const { user, getAccessToken } = useAuth();
  
  return (
    <DismissibleProvider 
      userId={user.id} 
      jwt={() => getAccessToken()}
      baseUrl="https://api.yourapp.com"
    >
      <YourApp />
    </DismissibleProvider>
  );
}

// With async JWT function
function AppWithAsyncAuth() {
  const { user, refreshAndGetToken } = useAuth();
  
  return (
    <DismissibleProvider 
      userId={user.id} 
      jwt={async () => await refreshAndGetToken()}
      baseUrl="https://api.yourapp.com"
    >
      <YourApp />
    </DismissibleProvider>
  );
}

See Custom HTTP Client for examples of using a custom client.

<Dismissible> Component

The main component for creating dismissible content.

Note: The <Dismissible> component renders null when an item is dismissed. For restore functionality, use the useDismissibleItem hook directly in custom implementations.

Props

| Prop | Type | Required | Description | |------|------|----------|-------------| | itemId | string | ✅ | Unique identifier for the dismissible item | | children | ReactNode | ✅ | Content to render when not dismissed | | onDismiss | () => void | ❌ | Callback fired when item is dismissed | | LoadingComponent | ComponentType<{itemId: string}> | ❌ | Custom loading component | | ErrorComponent | ComponentType<{itemId: string, error: Error}> | ❌ | Custom error component | | DismissButtonComponent | ComponentType<{onDismiss: () => Promise<void>, ariaLabel: string}> | ❌ | Custom dismiss button | | ignoreErrors | boolean | ❌ | Ignore errors and display component anyway (default: false) | | enableCache | boolean | ❌ | Enable localStorage caching (default: true) | | cachePrefix | string | ❌ | Cache key prefix (default: 'dismissible') | | cacheExpiration | number | ❌ | Cache expiration time in milliseconds |

Example

<Dismissible
  itemId="promo-banner"
  onDismiss={() => console.log('Banner dismissed')}
>
  <div className="promo">
    <h3>Special Offer!</h3>
    <p>Get 50% off your first order</p>
  </div>
</Dismissible>

useDismissibleItem Hook

For custom implementations and advanced use cases.

Parameters

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | itemId | string | ✅ | Unique identifier for the dismissible item | | options | object | ❌ | Configuration options |

Options

| Option | Type | Required | Description | |--------|------|----------|-------------| | enableCache | boolean | ❌ | Enable localStorage caching (default: true) | | cachePrefix | string | ❌ | Cache key prefix (default: 'dismissible') | | cacheExpiration | number | ❌ | Cache expiration time in milliseconds | | initialData | IDismissibleItem | ❌ | Initial data for the dismissible item |

Returns

| Property | Type | Description | |----------|------|-------------| | dismissedAt | string \| undefined | ISO date string when item was dismissed, or undefined | | dismiss | () => Promise<void> | Function to dismiss the item | | restore | () => Promise<void> | Function to restore a dismissed item | | isLoading | boolean | Loading state indicator | | error | Error \| undefined | Error state, if any | | item | IDismissibleItem \| undefined | The full dismissible item object |

Example

import { useDismissibleItem } from '@dismissible/react-client';

function CustomDismissible({ itemId, children }) {
  const { dismissedAt, dismiss, restore, isLoading, error } = useDismissibleItem(itemId);

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  if (dismissedAt) {
    return (
      <div>
        <p>This item was dismissed.</p>
        <button onClick={restore}>Restore</button>
      </div>
    );
  }

  return (
    <div>
      {children}
      <button onClick={dismiss}>
        Dismiss
      </button>
    </div>
  );
}

Usage Examples

Basic Dismissible Banner

import { DismissibleProvider, Dismissible } from '@dismissible/react-client';

function App() {
  const userId = getCurrentUserId();
  
  return (
    <DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
      <Dashboard />
    </DismissibleProvider>
  );
}

function Dashboard() {
  return (
    <Dismissible itemId="welcome-banner">
      <div className="alert alert-info">
        <h4>Welcome!</h4>
        <p>Thanks for joining our platform. Here are some quick tips to get started.</p>
      </div>
    </Dismissible>
  );
}

JWT Authentication Setup

For secure environments, configure JWT authentication:

import { DismissibleProvider, Dismissible } from '@dismissible/react-client';

function App() {
  const { user, getAccessToken } = useAuth();
  
  return (
    <DismissibleProvider 
      userId={user.id}
      jwt={() => getAccessToken()}
      baseUrl="https://api.yourapp.com"
    >
      <Dashboard />
    </DismissibleProvider>
  );
}

function Dashboard() {
  return (
    <div>
      <Dismissible itemId="user-welcome-banner">
        <div className="alert alert-info">
          <h4>Welcome back!</h4>
          <p>You have 3 new notifications.</p>
        </div>
      </Dismissible>
    </div>
  );
}

Custom Dismiss Button

import { Dismissible } from '@dismissible/react-client';

const CustomDismissButton = ({ onDismiss, ariaLabel }) => (
  <button
    onClick={onDismiss}
    className="custom-close-btn"
    aria-label={ariaLabel}
  >
    ✕
  </button>
);

function CustomBanner() {
  return (
    <Dismissible
      itemId="custom-banner"
      DismissButtonComponent={CustomDismissButton}
    >
      <div className="banner">
        <p>This banner has a custom dismiss button!</p>
      </div>
    </Dismissible>
  );
}

Custom Loading and Error Components

import { Dismissible } from '@dismissible/react-client';

const CustomLoader = ({ itemId }) => (
  <div className="spinner">
    <div className="bounce1"></div>
    <div className="bounce2"></div>
    <div className="bounce3"></div>
  </div>
);

const CustomError = ({ error }) => (
  <div className="error-card">
    <h4>Oops! Something went wrong</h4>
    <p>{error.message}</p>
    <button onClick={() => window.location.reload()}>
      Try Again
    </button>
  </div>
);

function AdvancedBanner() {
  return (
    <Dismissible
      itemId="advanced-banner"
      LoadingComponent={CustomLoader}
      ErrorComponent={CustomError}
    >
      <div className="banner">
        <p>This banner has custom loading and error states!</p>
      </div>
    </Dismissible>
  );
}

Multiple Dismissible Items

import { Dismissible } from '@dismissible/react-client';

function Dashboard() {
  return (
    <div>
      <Dismissible itemId="feature-announcement">
        <div className="alert alert-success">
          New feature: Dark mode is now available!
        </div>
      </Dismissible>

      <Dismissible itemId="maintenance-notice">
        <div className="alert alert-warning">
          Scheduled maintenance: Sunday 2AM-4AM EST
        </div>
      </Dismissible>

      <Dismissible itemId="survey-request">
        <div className="alert alert-info">
          Help us improve! Take our 2-minute survey.
        </div>
      </Dismissible>
    </div>
  );
}

Error Handling with ignoreErrors

import { Dismissible } from '@dismissible/react-client';

// Show content even if API fails
function RobustBanner() {
  return (
    <Dismissible
      itemId="important-announcement"
      ignoreErrors={true}
    >
      <div className="important-banner">
        <h3>Critical System Update</h3>
        <p>System maintenance scheduled for tonight. Please save your work.</p>
      </div>
    </Dismissible>
  );
}

Using the Hook for Complex Logic

import { useDismissibleItem } from '@dismissible/react-client';
import { useState, useEffect } from 'react';

function SmartNotification({ itemId, message, type = 'info' }) {
  const { dismissedAt, dismiss, isLoading } = useDismissibleItem(itemId);
  const [autoHide, setAutoHide] = useState(false);

  // Auto-hide after 10 seconds for info messages
  useEffect(() => {
    if (type === 'info' && !dismissedAt) {
      const timer = setTimeout(() => {
        setAutoHide(true);
        dismiss();
      }, 10000);
      
      return () => clearTimeout(timer);
    }
  }, [type, dismissedAt, dismiss]);

  if (dismissedAt || autoHide) {
    return null;
  }

  return (
    <div className={`notification notification-${type}`}>
      <span>{message}</span>
      <button 
        onClick={dismiss}
        disabled={isLoading}
        className="dismiss-btn"
      >
        {isLoading ? '...' : '×'}
      </button>
    </div>
  );
}

Restoring Dismissed Items

Use the restore function to bring back previously dismissed content:

import { useDismissibleItem } from '@dismissible/react-client';

function RestorableBanner({ itemId }) {
  const { dismissedAt, dismiss, restore, isLoading } = useDismissibleItem(itemId);

  if (dismissedAt) {
    return (
      <div className="dismissed-placeholder">
        <p>Banner was dismissed on {new Date(dismissedAt).toLocaleDateString()}</p>
        <button onClick={restore} disabled={isLoading}>
          {isLoading ? 'Restoring...' : 'Show Banner Again'}
        </button>
      </div>
    );
  }

  return (
    <div className="banner">
      <h3>Welcome!</h3>
      <p>This is a restorable banner.</p>
      <button onClick={dismiss} disabled={isLoading}>
        Dismiss
      </button>
    </div>
  );
}

Advanced Usage

Automatic Request Batching

The library automatically batches multiple dismissible item requests into a single API call, dramatically reducing network overhead when rendering pages with many dismissible components.

How It Works

Under the hood, Dismissible uses a BatchScheduler that implements DataLoader-style request coalescing:

  1. Request Collection: When multiple <Dismissible> components or useDismissibleItem hooks mount during the same render cycle, each request is queued rather than fired immediately.

  2. Microtask Scheduling: The scheduler uses queueMicrotask() to defer execution until after all synchronous code in the current JavaScript tick completes.

  3. Batch Execution: All queued requests are combined into a single batch API call (up to 50 items per batch, with automatic splitting for larger sets).

  4. Result Distribution: When the API responds, results are distributed back to each waiting component.

┌─────────────────────────────────────────────────────────────────┐
│                     Same JavaScript Tick                         │
├─────────────────────────────────────────────────────────────────┤
│  Component A          Component B          Component C          │
│  requests "banner"    requests "modal"     requests "tooltip"   │
│       │                    │                    │                │
│       └────────────────────┼────────────────────┘                │
│                            ▼                                     │
│                    ┌───────────────┐                            │
│                    │ BatchScheduler│                            │
│                    │    Queue      │                            │
│                    └───────┬───────┘                            │
│                            │                                     │
│                     queueMicrotask                               │
└────────────────────────────┼────────────────────────────────────┘
                             ▼
                ┌─────────────────────────┐
                │   Single API Call       │
                │   POST /v1/users/{id}/  │
                │        items/batch      │
                │   ["banner", "modal",   │
                │    "tooltip"]           │
                └─────────────────────────┘

Example: Dashboard with Multiple Dismissibles

// Without batching: 5 separate API calls
// With batching: 1 single API call containing all 5 item IDs

function Dashboard() {
  return (
    <div>
      <Dismissible itemId="welcome-banner">
        <WelcomeBanner />
      </Dismissible>
      
      <Dismissible itemId="feature-announcement">
        <FeatureAnnouncement />
      </Dismissible>
      
      <Dismissible itemId="survey-prompt">
        <SurveyPrompt />
      </Dismissible>
      
      <Dismissible itemId="upgrade-notice">
        <UpgradeNotice />
      </Dismissible>
      
      <Dismissible itemId="maintenance-alert">
        <MaintenanceAlert />
      </Dismissible>
    </div>
  );
}

Built-in Optimizations

The BatchScheduler includes several optimizations:

  • Request Deduplication: If the same itemId is requested multiple times in the same tick, only one request is made and the result is shared.
  • In-Memory Caching: Previously fetched items are cached in memory to avoid redundant API calls.
  • Cache Priming: Items loaded from localStorage are automatically primed in the batch cache.
  • Cache Sync: When items are dismissed or restored, the batch cache is updated to ensure consistency.

Using the Hook with Batching

The batching is completely transparent when using the useDismissibleItem hook:

function NotificationCenter() {
  // All three hooks will batch their requests into a single API call
  const notification1 = useDismissibleItem('notification-1');
  const notification2 = useDismissibleItem('notification-2');
  const notification3 = useDismissibleItem('notification-3');

  // Rendering logic...
}

Performance Impact

| Scenario | Without Batching | With Batching | |----------|------------------|---------------| | 5 dismissible items | 5 HTTP requests | 1 HTTP request | | 20 dismissible items | 20 HTTP requests | 1 HTTP request | | 100 dismissible items | 100 HTTP requests | 2 HTTP requests* |

* Batches are automatically split at 50 items to respect API limits

Custom HTTP Client

By default, Dismissible uses a built-in HTTP client powered by openapi-fetch. However, you can provide your own HTTP client implementation by passing a client prop to the DismissibleProvider. This is useful when you need:

  • Custom headers (correlation IDs, tracing, etc.)
  • Request/response interceptors
  • Use a different HTTP library (axios, ky, etc.)
  • Analytics and logging
  • Custom error handling

The DismissibleClient Interface

Your custom client must implement the DismissibleClient interface:

import type { DismissibleClient, DismissibleItem } from '@dismissible/react-client';

interface DismissibleClient {
  getOrCreate: (params: {
    userId: string;
    itemId: string;
    baseUrl: string;
    authHeaders: { Authorization?: string };
    signal?: AbortSignal;
  }) => Promise<DismissibleItem>;

  // Required for automatic batching - fetches multiple items in one API call
  batchGetOrCreate: (params: {
    userId: string;
    itemIds: string[];  // Array of item IDs (max 50)
    baseUrl: string;
    authHeaders: { Authorization?: string };
    signal?: AbortSignal;
  }) => Promise<DismissibleItem[]>;

  dismiss: (params: {
    userId: string;
    itemId: string;
    baseUrl: string;
    authHeaders: { Authorization?: string };
  }) => Promise<DismissibleItem>;

  restore: (params: {
    userId: string;
    itemId: string;
    baseUrl: string;
    authHeaders: { Authorization?: string };
  }) => Promise<DismissibleItem>;
}

Note: The batchGetOrCreate method is essential for the automatic request batching feature. When multiple components request items in the same render cycle, this method is called instead of multiple getOrCreate calls.

Example: Custom Client with Axios

import axios from 'axios';
import { v4 as uuid } from 'uuid';
import { DismissibleProvider } from '@dismissible/react-client';
import type { DismissibleClient } from '@dismissible/react-client';

const axiosClient: DismissibleClient = {
  getOrCreate: async ({ userId, itemId, baseUrl, authHeaders, signal }) => {
    const response = await axios.get(
      `${baseUrl}/v1/users/${userId}/items/${itemId}`,
      {
        headers: {
          ...authHeaders,
          'X-Correlation-ID': uuid(),
        },
        signal,
      }
    );
    return response.data.data;
  },

  // Batch endpoint for automatic request coalescing
  batchGetOrCreate: async ({ userId, itemIds, baseUrl, authHeaders, signal }) => {
    const response = await axios.post(
      `${baseUrl}/v1/users/${userId}/items/batch`,
      { itemIds },
      {
        headers: {
          ...authHeaders,
          'X-Correlation-ID': uuid(),
        },
        signal,
      }
    );
    return response.data.data;
  },

  dismiss: async ({ userId, itemId, baseUrl, authHeaders }) => {
    const response = await axios.delete(
      `${baseUrl}/v1/users/${userId}/items/${itemId}`,
      {
        headers: {
          ...authHeaders,
          'X-Correlation-ID': uuid(),
        },
      }
    );
    return response.data.data;
  },

  restore: async ({ userId, itemId, baseUrl, authHeaders }) => {
    const response = await axios.post(
      `${baseUrl}/v1/users/${userId}/items/${itemId}`,
      {},
      {
        headers: {
          ...authHeaders,
          'X-Correlation-ID': uuid(),
        },
      }
    );
    return response.data.data;
  },
};

function App() {
  return (
    <DismissibleProvider
      userId="user-123"
      baseUrl="https://api.yourapp.com"
      client={axiosClient}
    >
      <YourApp />
    </DismissibleProvider>
  );
}

Example: Custom Client with Logging

import type { DismissibleClient } from '@dismissible/react-client';

const loggingClient: DismissibleClient = {
  getOrCreate: async ({ userId, itemId, baseUrl, authHeaders, signal }) => {
    console.log(`[Dismissible] Fetching item: ${itemId} for user: ${userId}`);
    const startTime = performance.now();

    const response = await fetch(
      `${baseUrl}/v1/users/${userId}/items/${itemId}`,
      {
        method: 'GET',
        headers: authHeaders,
        signal,
      }
    );

    const data = await response.json();
    console.log(`[Dismissible] Fetched in ${performance.now() - startTime}ms`);

    if (!response.ok) {
      throw new Error(data.message || 'Failed to fetch dismissible item');
    }

    return data.data;
  },

  batchGetOrCreate: async ({ userId, itemIds, baseUrl, authHeaders, signal }) => {
    console.log(`[Dismissible] Batch fetching ${itemIds.length} items for user: ${userId}`);
    const startTime = performance.now();

    const response = await fetch(
      `${baseUrl}/v1/users/${userId}/items/batch`,
      {
        method: 'POST',
        headers: {
          ...authHeaders,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ itemIds }),
        signal,
      }
    );

    const data = await response.json();
    console.log(`[Dismissible] Batch fetched ${itemIds.length} items in ${performance.now() - startTime}ms`);

    if (!response.ok) {
      throw new Error(data.message || 'Failed to batch fetch dismissible items');
    }

    return data.data;
  },

  dismiss: async ({ userId, itemId, baseUrl, authHeaders }) => {
    console.log(`[Dismissible] Dismissing item: ${itemId}`);
    
    const response = await fetch(
      `${baseUrl}/v1/users/${userId}/items/${itemId}`,
      {
        method: 'DELETE',
        headers: authHeaders,
      }
    );

    const data = await response.json();
    
    if (!response.ok) {
      throw new Error(data.message || 'Failed to dismiss item');
    }

    console.log(`[Dismissible] Item dismissed at: ${data.data.dismissedAt}`);
    return data.data;
  },

  restore: async ({ userId, itemId, baseUrl, authHeaders }) => {
    console.log(`[Dismissible] Restoring item: ${itemId}`);
    
    const response = await fetch(
      `${baseUrl}/v1/users/${userId}/items/${itemId}`,
      {
        method: 'POST',
        headers: authHeaders,
      }
    );

    const data = await response.json();
    
    if (!response.ok) {
      throw new Error(data.message || 'Failed to restore item');
    }

    console.log(`[Dismissible] Item restored successfully`);
    return data.data;
  },
};

function App() {
  return (
    <DismissibleProvider
      userId="user-123"
      baseUrl="https://api.yourapp.com"
      client={loggingClient}
    >
      <YourApp />
    </DismissibleProvider>
  );
}

Example: Custom Client with Retry Logic

import type { DismissibleClient } from '@dismissible/react-client';

async function fetchWithRetry(
  url: string,
  options: RequestInit,
  retries = 3
): Promise<Response> {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const response = await fetch(url, options);
      if (response.ok || attempt === retries) {
        return response;
      }
    } catch (error) {
      if (attempt === retries) throw error;
    }
    // Exponential backoff
    await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 100));
  }
  throw new Error('Max retries reached');
}

const retryClient: DismissibleClient = {
  getOrCreate: async ({ userId, itemId, baseUrl, authHeaders, signal }) => {
    const response = await fetchWithRetry(
      `${baseUrl}/v1/users/${userId}/items/${itemId}`,
      { method: 'GET', headers: authHeaders, signal }
    );
    const data = await response.json();
    return data.data;
  },

  batchGetOrCreate: async ({ userId, itemIds, baseUrl, authHeaders, signal }) => {
    const response = await fetchWithRetry(
      `${baseUrl}/v1/users/${userId}/items/batch`,
      {
        method: 'POST',
        headers: { ...authHeaders, 'Content-Type': 'application/json' },
        body: JSON.stringify({ itemIds }),
        signal,
      }
    );
    const data = await response.json();
    return data.data;
  },

  dismiss: async ({ userId, itemId, baseUrl, authHeaders }) => {
    const response = await fetchWithRetry(
      `${baseUrl}/v1/users/${userId}/items/${itemId}`,
      { method: 'DELETE', headers: authHeaders }
    );
    const data = await response.json();
    return data.data;
  },

  restore: async ({ userId, itemId, baseUrl, authHeaders }) => {
    const response = await fetchWithRetry(
      `${baseUrl}/v1/users/${userId}/items/${itemId}`,
      { method: 'POST', headers: authHeaders }
    );
    const data = await response.json();
    return data.data;
  },
};

Styling

The library includes minimal default styles. You can override them or provide your own:

/* Default classes you can style */
.dismissible-container {
  /* Container for the dismissible content */
}

.dismissible-loading {
  /* Loading state */
}

.dismissible-error {
  /* Error state */
}

.dismissible-button {
  /* Default dismiss button */
}

Self-Hosting

Dismissible is designed to be self-hosted. You have full control over your data.

Option 1: Docker (Recommended)

The fastest way to get started:

docker run -p 3001:3001 dismissibleio/dismissible-api:latest

See the Docker documentation for production configuration.

Option 2: NestJS Module

Integrate directly into your existing NestJS application:

npm install @dismissible/nestjs-api

See the NestJS documentation for setup instructions.

Support

License

MIT © Dismissible

Changelog

See CHANGELOG.md for a detailed list of changes.