npm package: @getuserfeedback/react
Description: getuserfeedback React SDK
Latest version: 0.4.8

# @getuserfeedback/react

A secure and performant SDK for [getuserfeedback.com](https://getuserfeedback.com).

`@getuserfeedback/react` lets you easily add in-app messaging and micro-surveys optimized to help you connect with users, build trust, guide development, and avoid building in a vacuum.

> Using plain JavaScript or another framework? Check out [@getuserfeedback/sdk](https://www.npmjs.com/package/@getuserfeedback/sdk).

## Install

```bash
npm install @getuserfeedback/react
```

Requires `react >= 18`.

## Get started

### 1. Get an API key
Sign up on [getuserfeedback.com](https://getuserfeedback.com) and grab your key in Settings.

### 2. Add the provider

```tsx
import { GetUserFeedbackProvider } from "@getuserfeedback/react";

export function App() {
  return (
    <GetUserFeedbackProvider clientOptions={{ apiKey: "YOUR_API_KEY" }}>
      <AppRoutes />
    </GetUserFeedbackProvider>
  );
}
```

### 3. Start collecting responses

Pick one of our curated templates on [getuserfeedback.com/templates](https://getuserfeedback.com/templates) — from a post-signup welcome message to customer satisfaction surveys, feedback forms, and more. You can also customize everything or start from scratch.

### 4. Get feedback — improve product — repeat
Our widgets are optimized to get you *more* responses. Polished UI, crafted copy, templates, and a million subtle tricks. Result — you get more responses, make better roadmap calls, and find product-market fit faster.

### Bonus
Our AI-powered theme editor allows a complete overhaul of the widget look and feel with a single prompt. Demo: [getuserfeedback.com/themes](https://getuserfeedback.com/themes).

## Advanced use cases
### Feedback form
You can call flows imperatively, e.g., open a feedback form when the user clicks a dedicated button in your UI.

```tsx
import { useFlow } from "@getuserfeedback/react";

const CONTACT_SUPPORT_FLOW_ID = "YOUR_FLOW_ID";

export function ContactSupportMenuItem() {
  const { open } = useFlow({ flowId: CONTACT_SUPPORT_FLOW_ID });

  return (
    <button type="button" onClick={() => open()}>
      Contact support
    </button>
  );
}
```

### Bring your own UI
By default, flows are displayed in a floating container and [themed](https://getuserfeedback.com/themes) accordingly. Beyond that, you can replace the default floating container with your own. This gives you unprecedented customization options for a seamless user experience.

For example, if you're using shadcn/ui dialogs throughout your product, our widgets can be seamlessly rendered into them and appear just like any other part of your app.

```tsx
import { useFlow } from "@getuserfeedback/react";
import { Button, Dialog, DialogContent } from "ui";

export function FeedbackDialog() {
  const {
    isLoading,
    setOpen,
    shouldRenderContainer,
    containerRef,
    width,
    height
  } = useFlow({ flowId: "YOUR_FLOW_ID", container: "custom" });

  return (
    <>
      <Button
        type="button"
        onClick={() => setOpen(true)}
        disabled={isLoading}
      >
        Open feedback
      </Button>
      <Dialog open={shouldRenderContainer} onOpenChange={setOpen}>
        <DialogContent
          ref={containerRef}
          style={{ width, height }}
        />
      </Dialog>
    </>
  );
}
```

## API Overview

- `GetUserFeedbackProvider`
- `useGetUserFeedback()`
- `useFlow({ flowId, prefetchOnMount?, hideCloseButton?, container?: "default" | "custom" })`
- `useDefaultFlowContainer()`

## `GetUserFeedbackProvider`

Initialize the SDK client.

```tsx
import { GetUserFeedbackProvider } from "@getuserfeedback/react";

export function App() {
  return (
    <GetUserFeedbackProvider clientOptions={{ apiKey: "YOUR_API_KEY" }}>
      <AppRoutes />
    </GetUserFeedbackProvider>
  );
}
```

### Options

Pass SDK options via `clientOptions`:

- `apiKey` (`string`, required): your project API key.
- `colorScheme` (`"light" | "dark" | "system" | { autoDetectColorScheme: string[] }`, optional):
  controls widget theme mode. See [Dark mode](#dark-mode).
- `disableAutoLoad` (`boolean`, optional): if `true`, call `client.load()` manually.
- `disableTelemetry` (`boolean`, optional): disables anonymous telemetry used for performance and error monitoring.

### Delay widget load

If you want to manually control when the widget starts loading:

```tsx
import { useEffect } from "react";
import {
  GetUserFeedbackProvider,
  useGetUserFeedback,
} from "@getuserfeedback/react";

function LoadWidgetAfterConsent({
  hasResolvedConsent,
}: {
  hasResolvedConsent: boolean;
}) {
  const client = useGetUserFeedback();

  useEffect(() => {
    if (!hasResolvedConsent) {
      return;
    }

    client.load();
  }, [client, hasResolvedConsent]);

  return null;
}

export function App({ hasResolvedConsent }: { hasResolvedConsent: boolean }) {
  return (
    <GetUserFeedbackProvider
      clientOptions={{ apiKey: "YOUR_API_KEY", disableAutoLoad: true }}
    >
      <AppRoutes />
      <LoadWidgetAfterConsent hasResolvedConsent={hasResolvedConsent} />
    </GetUserFeedbackProvider>
  );
}
```

## Identify users

Use identity to unlock cohort targeting and attach responses to user profiles.

### `client.identify(userId, traits?)` or `client.identify(traits)`

```tsx
import { useEffect } from "react";
import { useGetUserFeedback } from "@getuserfeedback/react";

type User = {
  id: string;
  email?: string;
  firstName?: string;
  lastName?: string;
};

export function Identify({ user }: { user: User | null }) {
  const client = useGetUserFeedback();

  useEffect(() => {
    if (!user) {
      return;
    }

    client.identify(user.id, {
      email: user.email,
      firstName: user.firstName,
      lastName: user.lastName,
    });
  }, [client, user]);

  return null;
}
```

### `client.reset()`

Call this on logout to clear active identity state and auth (if you use auth):

```tsx
import { useGetUserFeedback } from "@getuserfeedback/react";
import { useAuth } from "./auth";

export function LogoutButton() {
  const { signOut } = useAuth();
  const { reset } = useGetUserFeedback();

  const handleLogout = async () => {
    await signOut();
    await reset();
  };

  return (
    <button type="button" onClick={handleLogout}>
      Log out
    </button>
  );
}
```

## Dark mode

If you're using industry-standard ways to indicate when dark mode is *on*, the widget will pick it up and automatically keep itself in sync. If you only have light mode, the widget will stay in light mode.

Default behavior:
- The widget observes `class` and `data-theme` attributes on both `html` and `body`.
- `class="dark"` / `data-theme="dark"` enables dark mode.
- `class="light"` / `data-theme="light"` enables light mode.
- `class="system"` / `data-theme="system"` follows the user's system preference (`prefers-color-scheme`).
- If nothing is detected, it falls back to `"light"`.

You can control the behavior using the `colorScheme` option when creating a client, and you can also update it later.

### Set a static color scheme at startup

```tsx
import { GetUserFeedbackProvider } from "@getuserfeedback/react";

export function App() {
  return (
    <GetUserFeedbackProvider
      clientOptions={{
        apiKey: "YOUR_API_KEY",
        colorScheme: "dark", // set to a static value
      }}
    >
      <AppRoutes />
    </GetUserFeedbackProvider>
  );
}
```

`colorScheme` supports:
- `{ autoDetectColorScheme: string[] }`
- `"light" | "dark" | "system"`

### Update color scheme at runtime

```tsx
import { useEffect } from "react";
import { useGetUserFeedback } from "@getuserfeedback/react";

type ThemePreference = "light" | "dark" | "system";

export function SyncWidgetTheme({
  themePreference,
}: {
  themePreference: ThemePreference;
}) {
  const client = useGetUserFeedback();

  useEffect(() => {
    client.configure({ colorScheme: themePreference });
  }, [client, themePreference]);

  return null;
}
```

You can also switch runtime behavior back to host-driven auto-detection:

```tsx
import { useEffect } from "react";
import { useGetUserFeedback } from "@getuserfeedback/react";

export function SyncWidgetToAppTheme({
  useAppTheme,
}: {
  useAppTheme: boolean;
}) {
  const client = useGetUserFeedback();

  useEffect(() => {
    if (!useAppTheme) {
      return;
    }

    client.configure({
      colorScheme: {
        autoDetectColorScheme: ["class", "data-theme"], // default
      },
    });
  }, [client, useAppTheme]);

  return null;
}
```

## Auth (optional)

You can run the SDK without auth. This is standard industry practice for client-side widgets like ours, so it is not enforced by default. However, we encourage you to implement auth for stricter controls.

Enable JWT validation in your workspace settings and pass a signed token from your app.

### Clerk example: set, refresh, and clear auth token

```tsx
import { useEffect } from "react";
import { useAuth } from "@clerk/clerk-react";
import { useGetUserFeedback } from "@getuserfeedback/react";

const AUTH_REFRESH_INTERVAL_MS = 5 * 60 * 1000;

// Mount once inside both <ClerkProvider> and <GetUserFeedbackProvider>.
export function GetUserFeedbackClerkAuth() {
  const client = useGetUserFeedback();
  const { isLoaded, userId, sessionId, getToken } = useAuth();

  useEffect(() => {
    if (!isLoaded) {
      return;
    }

    let isDisposed = false;

    const syncAuthToken = async () => {
      // Signed out: clear auth token.
      if (!userId) {
        await client.configure({ auth: { jwt: null } });
        return;
      }

      // Signed in: fetch/refresh Clerk JWT and pass it to the SDK.
      const token = await getToken({ template: "getuserfeedback" });
      if (isDisposed) {
        return;
      }

      if (!token) {
        await client.configure({ auth: { jwt: null } });
        return;
      }

      await client.configure({ auth: { jwt: { token } } });
    };

    syncAuthToken();
    const refreshTimer = window.setInterval(() => {
      syncAuthToken();
    }, AUTH_REFRESH_INTERVAL_MS);

    return () => {
      isDisposed = true;
      window.clearInterval(refreshTimer);
    };
  }, [client, getToken, isLoaded, sessionId, userId]);

  return null;
}
```

## Privacy and consent (GDPR, CCPA, etc)

Consent settings control non-essential data scopes: analytics, storage, personalization, ads.

By default, the widget starts with `granted` consent. For compliance with privacy regulations like GDPR and CCPA, you may want to choose a different default.

### Set default consent to `pending`
```tsx
import { GetUserFeedbackProvider } from "@getuserfeedback/react";

export function App() {
  return (
    <GetUserFeedbackProvider
      clientOptions={{
        apiKey: "YOUR_API_KEY",
        defaultConsent: "pending", // set to `pending`
      }}
    >
      <AppRoutes />
    </GetUserFeedbackProvider>
  );
}
```
With `pending` consent set by default, all non-essential data scopes are considered effectively denied.

### Update consent at runtime

For example, sync consent from your cookie banner or CMP state:

```tsx
import { useEffect } from "react";
import { useGetUserFeedback } from "@getuserfeedback/react";
import { useCookieConsent } from "./cookie-consent";

export function GetUserFeedbackConsentSync() {
  const client = useGetUserFeedback();
  const { hasAnswered, analyticsEnabled } = useCookieConsent();

  useEffect(() => {
    if (!hasAnswered) {
      client.configure({ consent: "pending" });
      return;
    }

    if (!analyticsEnabled) {
      client.configure({ consent: "denied" });
      return;
    }

    client.configure({
      consent: ["analytics.measurement", "analytics.storage"],
    });
  }, [analyticsEnabled, client, hasAnswered]);

  return null;
}
```

Or, when users save privacy settings, pass explicit granted scopes:

```tsx
import { useGetUserFeedback } from "@getuserfeedback/react";

type PrivacySettings = {
  analyticsEnabled: boolean;
  personalizedAdsEnabled: boolean;
};

export function useSavePrivacySettings() {
  const client = useGetUserFeedback();

  return async ({
    analyticsEnabled,
    personalizedAdsEnabled,
  }: PrivacySettings) => {
    const grantedScopes: Array<
      | "analytics.measurement"
      | "analytics.storage"
      | "ads.storage"
      | "ads.user_data"
      | "ads.personalization"
    > = [];

    if (analyticsEnabled) {
      grantedScopes.push("analytics.measurement", "analytics.storage");
    }

    if (personalizedAdsEnabled) {
      grantedScopes.push(
        "ads.storage",
        "ads.user_data",
        "ads.personalization",
      );
    }

    await client.configure({
      consent: grantedScopes.length > 0 ? grantedScopes : "denied",
    });
  };
}

```
`defaultConsent` supports:
- `"pending" | "granted" (default) | "denied" | "revoked"`
- or a granted-scope list like `["analytics.measurement", "analytics.storage"]`

### Impact of different consent settings

- Response collection: never affected.
- Imperative flow display: never affected.
- Cohort targeting: rules that depend on persisted activity, identity continuity, or storage-backed signals require both `analytics.measurement` and `analytics.storage`.
- Context targeting: rules based on current-page context (URL, referrer, timing, host signals) require `analytics.measurement` only.

## Advanced — control flows imperatively

### Open a flow

```tsx
import { useFlow } from "@getuserfeedback/react";

const BUG_REPORT_FLOW_ID = "YOUR_FLOW_ID";

export function ReportBugButton() {
  const { open } = useFlow({ flowId: BUG_REPORT_FLOW_ID });

  return (
    <button type="button" onClick={() => open()}>
      Report a bug
    </button>
  );
}
```

### Prefetch and prerender

Prefetching loads flow resources over the network, prerendering warms up the UI.

```tsx
import { useFlow } from "@getuserfeedback/react";

const FEEDBACK_FLOW_ID = "YOUR_FLOW_ID";

export function HelpMenuFeedbackItem() {
  const { prerender, open } = useFlow({
    flowId: FEEDBACK_FLOW_ID,
    prefetchOnMount: true,
  });

  return (
    <button
      type="button"
      onMouseEnter={() => prerender()}
      onFocus={() => prerender()}
      onClick={() => open()}
    >
      Share product feedback
    </button>
  );
}
```

## Links

- Docs: [getuserfeedback.com/docs](https://getuserfeedback.com/docs)
- Package versions (release history): [npm versions for `@getuserfeedback/react`](https://www.npmjs.com/package/@getuserfeedback/react?activeTab=versions)
- Report issues / support: [hello@getuserfeedback.com](mailto:hello@getuserfeedback.com)