From e5fbfe6c957a0ad8cee3f8c113ef6b30ffaa05f9 Mon Sep 17 00:00:00 2001 From: Jordan Morgan Date: Tue, 18 Aug 2026 13:28:10 -0500 Subject: [PATCH 1/4] docs(kmp): drop the per-hook delegate threading table Per Ian's review: the which-hook-lands-on-which-thread split is an implementation detail, not a contract, and may change between releases. Documenting it as a table invites people to build on it. Replaced with the guarantee that actually holds: delegate callbacks are not guaranteed to arrive on any particular thread, so treat every one as potentially background, hop to main for UI, stay thread-safe, and keep them short. Applied consistently across the four pages that repeated the old split (platform-differences, using-superwall-delegate, 3rd-party-analytics, handling-deep-links) so the docs do not contradict themselves. Co-Authored-By: Claude Opus 5 --- .../docs/kmp/guides/3rd-party-analytics.mdx | 8 ++--- .../docs/kmp/guides/handling-deep-links.mdx | 6 ++-- .../docs/kmp/guides/platform-differences.mdx | 32 ++++++------------- .../kmp/guides/using-superwall-delegate.mdx | 19 +++++------ 4 files changed, 24 insertions(+), 41 deletions(-) diff --git a/content/docs/kmp/guides/3rd-party-analytics.mdx b/content/docs/kmp/guides/3rd-party-analytics.mdx index 1a57107..1ae33e5 100644 --- a/content/docs/kmp/guides/3rd-party-analytics.mdx +++ b/content/docs/kmp/guides/3rd-party-analytics.mdx @@ -33,13 +33,11 @@ class AnalyticsDelegate : SuperwallDelegate { Superwall.delegate = AnalyticsDelegate() ``` - - -On Android, `handleSuperwallEvent` has **no guaranteed thread**. It adds no dispatcher hop, so it runs wherever the SDK tracked the event from. Usually that is a background thread. On iOS it is always main. +
-Two consequences: do not touch UI from it without hopping to main yourself, and do not assume it is off the main thread either. Keep the body cheap and non-blocking. See [Platform differences](/kmp/guides/platform-differences#delegate-threading). +`handleSuperwallEvent` is **not guaranteed** to arrive on any particular thread, and that may change between releases. Do not touch UI from it without hopping to main yourself, and do not assume it is off the main thread either. Keep the body cheap and non-blocking. See [Platform differences](/kmp/guides/platform-differences#delegate-threading). - +
## The event envelope diff --git a/content/docs/kmp/guides/handling-deep-links.mdx b/content/docs/kmp/guides/handling-deep-links.mdx index 2f9443c..24cef2a 100644 --- a/content/docs/kmp/guides/handling-deep-links.mdx +++ b/content/docs/kmp/guides/handling-deep-links.mdx @@ -98,11 +98,11 @@ class MyDelegate : SuperwallDelegate { Every case carries the `code` that was redeemed. - +
-`willRedeemLink` and `didRedeemLink` are analytics hooks: they arrive on a **background thread on Android**. The spinner calls above need a main-thread hop on Android. See [Platform differences](/kmp/guides/platform-differences#delegate-threading). +Like every delegate callback, `willRedeemLink` and `didRedeemLink` are not guaranteed to arrive on the main thread. The spinner calls above need a main-thread hop. See [Platform differences](/kmp/guides/platform-differences#delegate-threading). - +
## Superwall app links diff --git a/content/docs/kmp/guides/platform-differences.mdx b/content/docs/kmp/guides/platform-differences.mdx index 5ee9c49..fcdcdc8 100644 --- a/content/docs/kmp/guides/platform-differences.mdx +++ b/content/docs/kmp/guides/platform-differences.mdx @@ -57,37 +57,27 @@ Setting one of these on the other platform is harmless. It is ignored. This is the difference most likely to cause problems, because it is a runtime behavior rather than a missing method. -`SuperwallDelegate` callbacks are **not** forced onto the main thread. They arrive on whatever thread the native SDK called from, which splits cleanly: +
-| Hooks | Android | iOS | -| --- | --- | --- | -| `willPresentPaywall`, `didPresentPaywall`, `willDismissPaywall`, `didDismissPaywall`, `handleCustomPaywallAction`, `paywallWillOpenURL`, `paywallWillOpenDeepLink` | Main | Main | -| `subscriptionStatusDidChange`, `customerInfoDidChange`, `userAttributesDidChange`, `willRedeemLink`, `didRedeemLink` | Background | Main | -| `handleSuperwallEvent`, `handleLog` | **Not guaranteed** | Main | - -So the paywall lifecycle hooks are safe for UI work everywhere. The rest are not, on Android. +`SuperwallDelegate` callbacks are **not** guaranteed to arrive on any particular thread. The KMP layer adds no dispatching of its own, so each callback runs on whatever thread the native SDK invoked it from. That varies by callback and by platform, and it may change between releases, so do not build on the current behavior. - +Treat every delegate callback as if it could arrive on a background thread: -`handleSuperwallEvent` and `handleLog` have their own row because they are the least predictable. Neither adds a dispatcher hop on Android, so they run on whatever thread the SDK called from. `handleLog` is invoked inline wherever a log statement executes, which includes the main thread. `handleSuperwallEvent` inherits the context of the code that tracked the event. Usually that is a background thread, but do not rely on it in either direction: do not assume it is safe for UI, and do not assume it is off the main thread. - - +- Do not touch UI from a callback directly. Hop to the main thread yourself. +- Keep implementations thread-safe. Callbacks are not serialized against each other. +- Keep them short. They run synchronously on an SDK thread, so blocking in one slows the SDK. ```kotlin override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) { - // Fine: forwarding to an analytics SDK. + // Forwarding to an analytics SDK is fine from any thread. analytics.track(eventInfo.eventType.name) - // NOT fine on Android: this is a background thread. - // updateMyUi() + // Anything that touches UI needs to be dispatched to main. + scope.launch(Dispatchers.Main) { updateMyUi() } } ``` -If you need UI from one of those, hop yourself, or collect a flow instead: - -```kotlin -scope.launch(Dispatchers.Main) { updateMyUi() } -``` +
Collecting `Superwall.subscriptionStatusFlow` is usually the easier path when you want subscription @@ -96,8 +86,6 @@ scope.launch(Dispatchers.Main) { updateMyUi() } `lifecycleScope`) and you are safe; collect it on `Dispatchers.IO` and you are not. -Two more things to plan for: delegate implementations should be **thread-safe** (the analytics hooks are not serialized against each other), and they run **synchronously on an SDK thread**, so blocking in one slows the SDK. Keep them short. - `PaywallPresentationHandler` closures and the `register` `feature` closure are a different story: those *are* delivered on the main thread on both platforms, deliberately, because they gate UI. diff --git a/content/docs/kmp/guides/using-superwall-delegate.mdx b/content/docs/kmp/guides/using-superwall-delegate.mdx index 37171f2..0117cfb 100644 --- a/content/docs/kmp/guides/using-superwall-delegate.mdx +++ b/content/docs/kmp/guides/using-superwall-delegate.mdx @@ -91,22 +91,19 @@ override fun didRedeemLink(result: RedemptionResult) {} ## Threading - - -Delegate callbacks are **not** forced onto the main thread. They arrive on whatever thread the native SDK called from, and that is not the same on both platforms. +
-- **Paywall lifecycle hooks** arrive on the **main thread** on Android and iOS. Update UI from these directly. -- **State-change hooks** (`subscriptionStatusDidChange`, `customerInfoDidChange`, `userAttributesDidChange`, `willRedeemLink`, `didRedeemLink`) arrive on a **background thread on Android**, and on the main thread on iOS. -- **`handleSuperwallEvent` and `handleLog`** are **not guaranteed** either way on Android. Neither adds a dispatcher hop, so they run on whatever thread the SDK called from. `handleLog` is invoked inline wherever a log statement executes, which includes the main thread. On iOS both are on main. +Delegate callbacks are **not** guaranteed to arrive on any particular thread. The KMP layer adds no dispatching of its own, so each one runs on whatever thread the native SDK invoked it from. That can differ by callback and by platform, and it may change between releases. - +Treat every callback as if it could arrive on a background thread: -Two things this asks of your implementation: +1. **Do not touch UI directly.** Hop to the main thread yourself. +2. **Be thread-safe.** Callbacks are not serialized against each other. +3. **Be quick.** They run synchronously on an SDK thread, so blocking in one slows the SDK. -1. **Be thread-safe.** The analytics hooks are not serialized against each other. -2. **Be quick.** They run synchronously on an SDK thread, so blocking in one slows the SDK. +
-If you need UI work from an analytics hook, hop yourself: +If you need UI work from a callback, hop yourself: ```kotlin override fun subscriptionStatusDidChange(from: SubscriptionStatus, to: SubscriptionStatus) { From a89e22f022d204cb3191c9a7e66f89799d39f8f5 Mon Sep 17 00:00:00 2001 From: Jordan Morgan Date: Tue, 18 Aug 2026 13:36:55 -0500 Subject: [PATCH 2/4] docs(web): show React and JavaScript side by side on the configure page Per Ian's review: most Web SDK users are on React, so the configure page should show React first, in tabbed code blocks alongside plain JavaScript, rather than making React readers translate from createSuperwall. Every step now has a React / JavaScript CodeGroup with React as the default tab: SuperwallProvider vs createSuperwall, use(sw.ready) vs await sw.ready, useSignal(sw.configurationStatus) vs .value, and provider props vs the options object. Verified against packages/paywalls-react/src: provider props are CreateSuperwallOptions 1:1, and useSuperwall / useSignal are the exported names. The React deep-dive (Suspense boundary, read-once config) stays on web/react/provider; the configure page links there instead of repeating it. Co-Authored-By: Claude Opus 5 --- content/docs/web/quickstart/configure.mdx | 120 ++++++++++++++++++++-- 1 file changed, 114 insertions(+), 6 deletions(-) diff --git a/content/docs/web/quickstart/configure.mdx b/content/docs/web/quickstart/configure.mdx index bcf8e86..64c8dfb 100644 --- a/content/docs/web/quickstart/configure.mdx +++ b/content/docs/web/quickstart/configure.mdx @@ -13,21 +13,56 @@ The Web SDK is in beta and its API may change between releases. ## Create an instance -`createSuperwall` returns an instance synchronously and starts configuring in the background. +
-```ts +In React, wrap your app in `SuperwallProvider` once. In plain JavaScript, call `createSuperwall`. Both return an instance synchronously and start configuring in the background. + +
+ + + +```tsx React +import { SuperwallProvider } from "@superwall/paywalls-react"; + +export function Root() { + return ( + + + + ); +} +``` + +```ts JavaScript import { createSuperwall } from "@superwall/paywalls-js"; const sw = createSuperwall({ apiKey: "pk_…" }); ``` -Do this once, as early as your app boots. Keep the returned instance around, or use the [named exports](#named-exports) and let the SDK hold it for you. + + +Do this once, as early as your app boots. In React the provider holds the instance for you; reach it from any component with `useSuperwall()`. In plain JavaScript, keep the returned instance around, or use the [named exports](#named-exports) and let the SDK hold it for you. ## Wait for `ready` before presenting `register` waits for identity to hydrate, but it does not wait for configuration. Called before config lands, it returns `{ type: "error" }` carrying a `PaywallNotAvailableError`. -```ts + + +```tsx React +import { use } from "react"; +import { useSuperwall, usePlacement } from "@superwall/paywalls-react"; + +function Checkout() { + const sw = useSuperwall(); + use(sw.ready); // suspends until configured + + const { register } = usePlacement(); + return ; +} +``` + +```ts JavaScript const sw = createSuperwall({ apiKey: "pk_…" }); await sw.ready; @@ -35,6 +70,14 @@ await sw.ready; const result = await sw.register({ placement: "checkout" }); ``` + + +
+ +In React, `use(sw.ready)` suspends the component, so put a `` boundary above it. [Gating render on configuration](/web/react/provider#gating-render-on-configuration) covers the boundary and its error handling. + +
+ Reads never block. `sw.user.id.value` and `sw.subscriptionStatus.value` return synchronously at any time. Before hydration lands they return defaults (`""` and `{ status: "UNKNOWN" }`). Persisted state replaces the defaults shortly after. @@ -46,7 +89,24 @@ Reads never block. `sw.user.id.value` and `sw.subscriptionStatus.value` return s **Check `configurationStatus`, not `ready`, to detect failure.** A failed config fetch is swallowed internally: `sw.ready` still resolves, and `sw.configurationStatus` becomes `"failed"`. -```ts + + +```tsx React +import { useSuperwall, useSignal } from "@superwall/paywalls-react"; + +function ConfigGuard({ children }) { + const sw = useSuperwall(); + const status = useSignal(sw.configurationStatus); + + if (status === "failed") { + // Superwall could not configure. Paywalls will not present. + return null; + } + return children; +} +``` + +```ts JavaScript await sw.ready; if (sw.configurationStatus.value === "failed") { @@ -54,11 +114,41 @@ if (sw.configurationStatus.value === "failed") { } ``` + +
## Options -```ts +
+ +`SuperwallProvider` accepts every option `createSuperwall` does, as props. The two are the same configuration surface. + +
+ + + +```tsx React + "…", + }} +> + + +``` + +```ts JavaScript const sw = createSuperwall({ apiKey: "pk_…", options: { @@ -76,6 +166,8 @@ const sw = createSuperwall({ }); ``` + + | Option | Purpose | | --- | --- | | `apiKey` | Your `pk_…` publishable key. Required. | @@ -91,6 +183,16 @@ const sw = createSuperwall({ fingerprinting. +
+ + + In React, the provider reads these props once, on first mount for a given `apiKey`. Changing them + afterwards does not reconfigure the SDK. See + [Configuration is read once](/web/react/provider#configuration-is-read-once). + + +
+ ## Named exports Instead of threading the instance through your app, import the namespaces directly. The first `createSuperwall` call registers the default instance, and these bind to it. @@ -106,6 +208,12 @@ const result = await register({ placement: "checkout" }); Importing only `user` lets bundlers drop the rest as dead code. +
+ +React apps do not need this. `SuperwallProvider` already holds the instance, and the [hooks](/web/react/hooks) reach it through context. + +
+ ## Multiple instances Creating more than one instance is supported. Useful for tests, Storybook, and multi-tenant edge workers. The default instance is the first one created in the process, and it is the one the named exports target. From 2dc5ea697ea5ed4d34e5842d9877e6793f289faf Mon Sep 17 00:00:00 2001 From: Jordan Morgan Date: Tue, 18 Aug 2026 13:55:29 -0500 Subject: [PATCH 3/4] docs(kmp,web): correct threading mechanism claims after source audit The threading rewrite asserted "the KMP layer adds no dispatching of its own," which is only true on Android. On iOS every delegate callback goes through scope.launch on Dispatchers.Main.immediate (iosMain/.../DelegateAdapter.kt:43-51), so the callbacks are dispatched and serialized on main there. Two dependent claims fell with it: "not serialized against each other" and "slows the SDK, not the frame budget" were Android-only reasoning stated as universal. The guidance is unchanged (treat every callback as possibly background, hop for UI, stay thread-safe, keep it short); the text now stops asserting how that comes about. Both samples also referenced an undefined `scope`; they now show a delegate class that receives one. Web: typed the ConfigGuard prop so the tsx sample passes noImplicitAny. Co-Authored-By: Claude Opus 5 --- .../docs/kmp/guides/platform-differences.mdx | 20 ++++++++++--------- .../kmp/guides/using-superwall-delegate.mdx | 16 ++++++++------- content/docs/web/quickstart/configure.mdx | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/content/docs/kmp/guides/platform-differences.mdx b/content/docs/kmp/guides/platform-differences.mdx index fcdcdc8..5fe9a01 100644 --- a/content/docs/kmp/guides/platform-differences.mdx +++ b/content/docs/kmp/guides/platform-differences.mdx @@ -59,21 +59,23 @@ This is the difference most likely to cause problems, because it is a runtime be
-`SuperwallDelegate` callbacks are **not** guaranteed to arrive on any particular thread. The KMP layer adds no dispatching of its own, so each callback runs on whatever thread the native SDK invoked it from. That varies by callback and by platform, and it may change between releases, so do not build on the current behavior. +`SuperwallDelegate` callbacks are **not** guaranteed to arrive on any particular thread. Which thread a given callback lands on depends on the callback and the platform, and it may change between releases, so do not build on the current behavior. Treat every delegate callback as if it could arrive on a background thread: - Do not touch UI from a callback directly. Hop to the main thread yourself. -- Keep implementations thread-safe. Callbacks are not serialized against each other. -- Keep them short. They run synchronously on an SDK thread, so blocking in one slows the SDK. +- Keep implementations thread-safe. Do not assume callbacks are serialized against each other. +- Keep them short. A callback runs before the SDK continues, so blocking in one blocks the SDK, and on whatever thread that happens to be. ```kotlin -override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) { - // Forwarding to an analytics SDK is fine from any thread. - analytics.track(eventInfo.eventType.name) - - // Anything that touches UI needs to be dispatched to main. - scope.launch(Dispatchers.Main) { updateMyUi() } +class MyDelegate(private val scope: CoroutineScope) : SuperwallDelegate { + override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) { + // Forwarding to an analytics SDK is fine from any thread. + analytics.track(eventInfo.eventType.name) + + // Anything that touches UI needs to be dispatched to main. + scope.launch(Dispatchers.Main) { updateMyUi() } + } } ``` diff --git a/content/docs/kmp/guides/using-superwall-delegate.mdx b/content/docs/kmp/guides/using-superwall-delegate.mdx index 0117cfb..4fc550f 100644 --- a/content/docs/kmp/guides/using-superwall-delegate.mdx +++ b/content/docs/kmp/guides/using-superwall-delegate.mdx @@ -93,22 +93,24 @@ override fun didRedeemLink(result: RedemptionResult) {}
-Delegate callbacks are **not** guaranteed to arrive on any particular thread. The KMP layer adds no dispatching of its own, so each one runs on whatever thread the native SDK invoked it from. That can differ by callback and by platform, and it may change between releases. +Delegate callbacks are **not** guaranteed to arrive on any particular thread. Which thread a given callback lands on depends on the callback and the platform, and it may change between releases. Treat every callback as if it could arrive on a background thread: 1. **Do not touch UI directly.** Hop to the main thread yourself. -2. **Be thread-safe.** Callbacks are not serialized against each other. -3. **Be quick.** They run synchronously on an SDK thread, so blocking in one slows the SDK. +2. **Be thread-safe.** Do not assume callbacks are serialized against each other. +3. **Be quick.** A callback runs before the SDK continues, so blocking in one blocks the SDK.
-If you need UI work from a callback, hop yourself: +If you need UI work from a callback, hop yourself. Give the delegate a scope to launch on: ```kotlin -override fun subscriptionStatusDidChange(from: SubscriptionStatus, to: SubscriptionStatus) { - scope.launch(Dispatchers.Main) { - updateUi(to) +class MyDelegate(private val scope: CoroutineScope) : SuperwallDelegate { + override fun subscriptionStatusDidChange(from: SubscriptionStatus, to: SubscriptionStatus) { + scope.launch(Dispatchers.Main) { + updateUi(to) + } } } ``` diff --git a/content/docs/web/quickstart/configure.mdx b/content/docs/web/quickstart/configure.mdx index 64c8dfb..7991467 100644 --- a/content/docs/web/quickstart/configure.mdx +++ b/content/docs/web/quickstart/configure.mdx @@ -94,7 +94,7 @@ Reads never block. `sw.user.id.value` and `sw.subscriptionStatus.value` return s ```tsx React import { useSuperwall, useSignal } from "@superwall/paywalls-react"; -function ConfigGuard({ children }) { +function ConfigGuard({ children }: { children: React.ReactNode }) { const sw = useSuperwall(); const status = useSignal(sw.configurationStatus); From 491c82d618b53e0323668ecfb7552c8f1ad396f1 Mon Sep 17 00:00:00 2001 From: Jordan Morgan Date: Tue, 18 Aug 2026 14:13:27 -0500 Subject: [PATCH 4/4] docs(kmp,web): strip review highlights Co-Authored-By: Claude Opus 5 --- .../docs/kmp/guides/3rd-party-analytics.mdx | 4 ++-- .../docs/kmp/guides/handling-deep-links.mdx | 4 ++-- .../docs/kmp/guides/platform-differences.mdx | 4 ++-- .../kmp/guides/using-superwall-delegate.mdx | 4 ++-- content/docs/web/quickstart/configure.mdx | 20 ------------------- 5 files changed, 8 insertions(+), 28 deletions(-) diff --git a/content/docs/kmp/guides/3rd-party-analytics.mdx b/content/docs/kmp/guides/3rd-party-analytics.mdx index 1ae33e5..3ec45be 100644 --- a/content/docs/kmp/guides/3rd-party-analytics.mdx +++ b/content/docs/kmp/guides/3rd-party-analytics.mdx @@ -33,11 +33,11 @@ class AnalyticsDelegate : SuperwallDelegate { Superwall.delegate = AnalyticsDelegate() ``` -
+ `handleSuperwallEvent` is **not guaranteed** to arrive on any particular thread, and that may change between releases. Do not touch UI from it without hopping to main yourself, and do not assume it is off the main thread either. Keep the body cheap and non-blocking. See [Platform differences](/kmp/guides/platform-differences#delegate-threading). -
+ ## The event envelope diff --git a/content/docs/kmp/guides/handling-deep-links.mdx b/content/docs/kmp/guides/handling-deep-links.mdx index 24cef2a..080e70d 100644 --- a/content/docs/kmp/guides/handling-deep-links.mdx +++ b/content/docs/kmp/guides/handling-deep-links.mdx @@ -98,11 +98,11 @@ class MyDelegate : SuperwallDelegate { Every case carries the `code` that was redeemed. -
+ Like every delegate callback, `willRedeemLink` and `didRedeemLink` are not guaranteed to arrive on the main thread. The spinner calls above need a main-thread hop. See [Platform differences](/kmp/guides/platform-differences#delegate-threading). -
+ ## Superwall app links diff --git a/content/docs/kmp/guides/platform-differences.mdx b/content/docs/kmp/guides/platform-differences.mdx index 5fe9a01..732a1ab 100644 --- a/content/docs/kmp/guides/platform-differences.mdx +++ b/content/docs/kmp/guides/platform-differences.mdx @@ -57,7 +57,7 @@ Setting one of these on the other platform is harmless. It is ignored. This is the difference most likely to cause problems, because it is a runtime behavior rather than a missing method. -
+ `SuperwallDelegate` callbacks are **not** guaranteed to arrive on any particular thread. Which thread a given callback lands on depends on the callback and the platform, and it may change between releases, so do not build on the current behavior. @@ -79,7 +79,7 @@ class MyDelegate(private val scope: CoroutineScope) : SuperwallDelegate { } ``` -
+ Collecting `Superwall.subscriptionStatusFlow` is usually the easier path when you want subscription diff --git a/content/docs/kmp/guides/using-superwall-delegate.mdx b/content/docs/kmp/guides/using-superwall-delegate.mdx index 4fc550f..ac8827b 100644 --- a/content/docs/kmp/guides/using-superwall-delegate.mdx +++ b/content/docs/kmp/guides/using-superwall-delegate.mdx @@ -91,7 +91,7 @@ override fun didRedeemLink(result: RedemptionResult) {} ## Threading -
+ Delegate callbacks are **not** guaranteed to arrive on any particular thread. Which thread a given callback lands on depends on the callback and the platform, and it may change between releases. @@ -101,7 +101,7 @@ Treat every callback as if it could arrive on a background thread: 2. **Be thread-safe.** Do not assume callbacks are serialized against each other. 3. **Be quick.** A callback runs before the SDK continues, so blocking in one blocks the SDK. -
+ If you need UI work from a callback, hop yourself. Give the delegate a scope to launch on: diff --git a/content/docs/web/quickstart/configure.mdx b/content/docs/web/quickstart/configure.mdx index 7991467..56c6f38 100644 --- a/content/docs/web/quickstart/configure.mdx +++ b/content/docs/web/quickstart/configure.mdx @@ -13,12 +13,8 @@ The Web SDK is in beta and its API may change between releases. ## Create an instance -
- In React, wrap your app in `SuperwallProvider` once. In plain JavaScript, call `createSuperwall`. Both return an instance synchronously and start configuring in the background. -
- ```tsx React @@ -72,12 +68,8 @@ const result = await sw.register({ placement: "checkout" }); -
- In React, `use(sw.ready)` suspends the component, so put a `` boundary above it. [Gating render on configuration](/web/react/provider#gating-render-on-configuration) covers the boundary and its error handling. -
- Reads never block. `sw.user.id.value` and `sw.subscriptionStatus.value` return synchronously at any time. Before hydration lands they return defaults (`""` and `{ status: "UNKNOWN" }`). Persisted state replaces the defaults shortly after. @@ -120,12 +112,8 @@ if (sw.configurationStatus.value === "failed") { ## Options -
- `SuperwallProvider` accepts every option `createSuperwall` does, as props. The two are the same configuration surface. -
- ```tsx React @@ -183,16 +171,12 @@ const sw = createSuperwall({ fingerprinting.
-
- In React, the provider reads these props once, on first mount for a given `apiKey`. Changing them afterwards does not reconfigure the SDK. See [Configuration is read once](/web/react/provider#configuration-is-read-once). -
- ## Named exports Instead of threading the instance through your app, import the namespaces directly. The first `createSuperwall` call registers the default instance, and these bind to it. @@ -208,12 +192,8 @@ const result = await register({ placement: "checkout" }); Importing only `user` lets bundlers drop the rest as dead code. -
- React apps do not need this. `SuperwallProvider` already holds the instance, and the [hooks](/web/react/hooks) reach it through context. -
- ## Multiple instances Creating more than one instance is supported. Useful for tests, Storybook, and multi-tenant edge workers. The default instance is the first one created in the process, and it is the one the named exports target.