Logo

React SDK

React hooks and components for integrating Quotient into React applications

Installation

npm install @quotientjs/react

QuotientProvider

Context provider that gives child components the Quotient client. Create the client once with QuotientClient.init() in a file of its own, then pass it to the provider. Creating it at the top of a module is safe even in apps that render on the server: the client does nothing until it's used in the browser.

// lib/quotient.ts
import { QuotientClient } from "@quotientjs/react";

export const quotient = QuotientClient.init({
  apiKey: "pk_your_public_api_key",
});
import { QuotientProvider } from "@quotientjs/react";
import { quotient } from "./lib/quotient";

function App() {
  return <QuotientProvider client={quotient}>{children}</QuotientProvider>;
}
PropTypeRequiredDescription
clientQuotientClientYesThe client created with QuotientClient.init()

Use the client in effects and event handlers, not while a component renders. On the server the client exists but can't do anything, so calling it during a server render throws.

Page views

The client tracks page views when it starts and on every navigation. Choose which pages are tracked with the pageViews option of QuotientClient.init():

export const quotient = QuotientClient.init({
  apiKey: "pk_your_public_api_key",
  // Track every page except the logged-in app
  pageViews: (url) => !url.pathname.startsWith("/app"),
});

Set pageViews: false if another integration already sends Quotient page views from the same app.

Passing client options (deprecated)

clientOptions and autoTrackPageViews are deprecated. Create the client with QuotientClient.init() and pass it as client instead.

useQuotient

Access the Quotient client and context within React components.

import { useQuotient } from "@quotientjs/react";

function MyComponent() {
  const {
    client, // The QuotientClient passed to the provider
    error, // Error or null if initialization failed
    reset, // () => void: Clear storage and start fresh
  } = useQuotient();
}
FieldTypeDescription
clientQuotientClient | nullThe client passed to the provider. null only with the deprecated clientOptions, until the client is created
errorError | nullSet if initialization failed
reset() => voidClear all stored data and give the browser a new device ID

useBlogs

Fetch a paginated list of blogs within a client component. Must be inside a <QuotientProvider>.

ParamTypeRequiredDescription
pagenumberNoPage number
limitnumberNoResults per page
statusesBlogStatus[]NoFilter by status
"use client";
import { BlogStatus, useBlogs } from "@quotientjs/react";

export default function BlogList({
  page,
  limit,
  statuses,
}: {
  page: number;
  limit: number;
  statuses: BlogStatus[];
}) {
  const { data, isLoading } = useBlogs({
    page,
    limit,
    statuses,
  });

  const { blogs, pageData } = data;
  return (
    {/* Render blog list */}
  );
}

Common Patterns

User Identification on Login

"use client";
import { useQuotient } from "@quotientjs/react";

function LoginHandler() {
  const { client } = useQuotient();

  const handleLogin = async (user) => {
    await client?.audience.people.upsert({
      emailAddress: user.email,
      firstName: user.firstName,
      lastName: user.lastName,
      lists: ["customers"],
    });
  };

  return <button onClick={() => handleLogin(user)}>Login</button>;
}

Marketing Preference Center

"use client";
import { useQuotient } from "@quotientjs/react";
import { useState } from "react";

function EmailPreferences({ userEmail }) {
  const [state, setState] = useState("SUBSCRIBED");
  const [lists, setLists] = useState(["newsletter", "promotions"]);
  const { client } = useQuotient();

  const updatePreferences = async () => {
    try {
      await client.audience.people.upsert({
        emailAddress: userEmail,
        emailSubscriptionStatus: state,
        lists: state === "SUBSCRIBED" ? lists : [],
      });

      toast.success("Preferences updated");
    } catch (error) {
      toast.error("Failed to update preferences");
    }
  };

  return (
    <div>
      <label>
        <input
          type="radio"
          checked={state === "SUBSCRIBED"}
          onChange={() => setState("SUBSCRIBED")}
        />
        Subscribed - Receive marketing emails
      </label>

      <label>
        <input
          type="radio"
          checked={state === "UNSUBSCRIBED"}
          onChange={() => setState("UNSUBSCRIBED")}
        />
        Unsubscribed - No marketing emails
      </label>

      {state === "SUBSCRIBED" && (
        <div>
          <h3>Email Lists:</h3>
          <label>
            <input
              type="checkbox"
              checked={lists.includes("newsletter")}
              onChange={(e) => {
                if (e.target.checked) {
                  setLists([...lists, "newsletter"]);
                } else {
                  setLists(lists.filter((l) => l !== "newsletter"));
                }
              }}
            />
            Weekly Newsletter
          </label>
          {/* More list options... */}
        </div>
      )}

      <button onClick={updatePreferences}>Save Preferences</button>
    </div>
  );
}

Next Steps