CustomerInfo Update Listener in React Native

Overview

In react-native-purchases, a CustomerInfo update listener is a callback that RevenueCat invokes whenever it detects a change to the current user's CustomerInfo, for example after a purchase, a subscription renewal, a restore, or a background refresh. It is the recommended way to keep your UI (paywall, premium state, settings screen) in sync with the user's entitlements without polling.

The API has two methods:

  • Purchases.addCustomerInfoUpdateListener(listener): registers a callback. Returns void.
  • Purchases.removeCustomerInfoUpdateListener(listener): unregisters it. Returns a boolean.
Prerequisites: The RevenueCat SDK must already be installed and configured with Purchases.configure(...). See the React Native codelab and the Configure the SDK guide for setup.

addCustomerInfoUpdateListener

Register a listener after the SDK is configured. The callback receives the latest CustomerInfo object every time it changes:

typescript
import Purchases, { CustomerInfo } from 'react-native-purchases';

// Define the listener once so you keep a stable reference.
const onCustomerInfoUpdate = (customerInfo: CustomerInfo) => {
  const isPro = typeof customerInfo.entitlements.active['premium'] !== 'undefined';
  console.log('CustomerInfo changed. Pro active?', isPro);
  // Update your app state / context here.
};

Purchases.addCustomerInfoUpdateListener(onCustomerInfoUpdate);

The type of the callback is CustomerInfoUpdateListener = (customerInfo: CustomerInfo) => void. You can register more than one listener; each is called on every update.

removeCustomerInfoUpdateListener

To stop receiving updates, pass the same function reference back to removeCustomerInfoUpdateListener. It returns a boolean:

typescript
const removed: boolean = Purchases.removeCustomerInfoUpdateListener(onCustomerInfoUpdate);
// removed === true  -> the listener was found and removed
// removed === false -> no matching listener (you passed a different function instance)
The reference must match. removeCustomerInfoUpdateListener compares by function identity. If you pass an inline arrow function (a new instance) you will get false and the original listener stays attached. Always store the listener in a variable, useRef, or module scope so you can remove the exact same instance.

The useEffect Cleanup Pattern (Recommended)

In a React component, register the listener in useEffect and remove it in the cleanup function so it is torn down when the component unmounts. This prevents duplicate listeners and memory leaks during navigation:

tsx
import { useEffect, useState } from 'react';
import Purchases, { CustomerInfo } from 'react-native-purchases';

export function usePremiumStatus() {
  const [isPro, setIsPro] = useState(false);

  useEffect(() => {
    // 1) Read the current state once.
    Purchases.getCustomerInfo().then((info) => {
      setIsPro(typeof info.entitlements.active['premium'] !== 'undefined');
    });

    // 2) Subscribe to future changes.
    const listener = (info: CustomerInfo) => {
      setIsPro(typeof info.entitlements.active['premium'] !== 'undefined');
    };
    Purchases.addCustomerInfoUpdateListener(listener);

    // 3) Clean up on unmount.
    return () => {
      Purchases.removeCustomerInfoUpdateListener(listener);
    };
  }, []);

  return isPro;
}

The empty dependency array ([]) is important: it ensures the listener is added once and removed once. If you add the listener outside useEffect or with changing dependencies, you can end up with multiple listeners firing for a single change.

Listener vs getCustomerInfo

Use the right tool for the job:

Need Use
Current entitlement state right now Purchases.getCustomerInfo() (one-shot, cached)
React to renewals / purchases / restores live addCustomerInfoUpdateListener
Force a fresh fetch (ignore cache) invalidateCustomerInfoCache() then getCustomerInfo()

See Get CustomerInfo & refresh the cache for the fetch side of this API.

Common Pitfalls

Adding the listener before configure. Register the listener only after Purchases.configure(...) has run. Adding it earlier means it may never fire. Configure as early as possible (for example in your app entry point) and add listeners afterward.
The listener does not fire on registration. It only fires on the next change. Always call getCustomerInfo() once to seed your initial UI state, then let the listener handle updates.
Re-adding on every render. Defining the listener inline in the component body (not in useEffect) re-registers it on each render and the cleanup never matches. Keep a single stable reference.

FAQ

What does removeCustomerInfoUpdateListener return?
A boolean. true if the listener reference was found and removed; false if no matching listener was registered.

How do I remove the listener in a class component?
Store the reference on the instance (this.listener = ...), call Purchases.addCustomerInfoUpdateListener(this.listener) in componentDidMount, and Purchases.removeCustomerInfoUpdateListener(this.listener) in componentWillUnmount.

Why is my listener not firing?
Usually because it was added before configure(), or because nothing has actually changed yet. Purchases, renewals, and restores trigger it; simply opening the app does not.

Related Guides