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. Returnsvoid.Purchases.removeCustomerInfoUpdateListener(listener): unregisters it. Returns aboolean.
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:
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:
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)
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:
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
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.
getCustomerInfo() once to seed your initial UI state, then let the listener handle
updates.
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
- Get CustomerInfo & Refresh the Cache: fetch entitlements and force a refresh
- Configure the RevenueCat SDK: apiKey and appUserID setup
- React Native In-App Purchases Tutorial: full end-to-end integration
- RevenueCat Docs: CustomerInfo: official reference