Skip to content

fix stripe_web: store card element div globally to prevent mounting errors#2346

Open
hifiaz wants to merge 1 commit intoflutter-stripe:mainfrom
hifiaz:main
Open

fix stripe_web: store card element div globally to prevent mounting errors#2346
hifiaz wants to merge 1 commit intoflutter-stripe:mainfrom
hifiaz:main

Conversation

@hifiaz
Copy link

@hifiaz hifiaz commented Feb 18, 2026

The card element div was being recreated on each mount, causing issues with Stripe's element mounting. Store it globally and clear on dispose to ensure consistent mounting and unmounting.

Summary by CodeRabbit

  • Bug Fixes
    • Improved card field initialization and mounting logic to ensure proper container reference availability before rendering the payment card element.
    • Enhanced resource cleanup during widget disposal.

The card element div was being recreated on each mount, causing issues with Stripe's element mounting. Store it globally and clear on dispose to ensure consistent mounting and unmounting.
@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

Modified the Stripe card field widget to cache the card container element and defer mounting the card element until after the container reference is established, updating the disposal logic to clear the cached reference.

Changes

Cohort / File(s) Summary
Card Field Lifecycle
packages/stripe_web/lib/src/widgets/card_field.dart
Added private cache for card container div element, deferred Stripe card element mounting until container is available, and updated disposal to clear cached reference. Removed id assignment but retained border styling.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A rabbit hops through the Stripe display,
Caching the card div to save the day,
Mount with intention, dispose with care—
The element lifecycle's perfectly there! 🐰✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: storing the card element div globally to prevent mounting errors, which aligns with the core objective of the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/stripe_web/lib/src/widgets/card_field.dart`:
- Line 14: The top-level variable _cardElementDiv is shared across
WebStripeCardState instances and races between the HtmlElementView factory,
initStripe's post-frame callback, and dispose; change _cardElementDiv from a
file-scope variable into an instance field on WebStripeCardState so each state
owns its own HTMLDivElement, update the HtmlElementView factory to use a unique
per-instance viewType key that the factory closure captures (so it writes the
correct instance field), and adjust initStripe (the post-frame callback) and
dispose to reference the instance field instead of the top-level one to avoid
cross-instance clobbering or nulling.
- Around line 90-93: When the early-return checks _cardElementDiv for null (in
the CardField widget where _cardElementDiv is referenced), add a diagnostic log
using the existing dart:developer import instead of silently returning; update
the conditional in the method that contains the snippet (search for
_cardElementDiv and the surrounding code in CardField/State) to call log() (or
log with a level/message) describing that the container is null and why this
might occur before returning, so callers/debuggers see a clear diagnostic when
the element is missing.


import '../../flutter_stripe_web.dart';

web.HTMLDivElement? _cardElementDiv;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Global _cardElementDiv is unsafe during concurrent instances or interleaved navigation transitions

_cardElementDiv is a top-level file-scope variable shared by every WebStripeCardState instance. Two code paths race on it:

Event Effect on _cardElementDiv
Platform view factory (per HtmlElementView render) writes the newly-created div
Inner addPostFrameCallback in initStripe reads it to mount the Stripe element
dispose() nulls it

If a second WebCardField is alive at the same time (e.g., the old and new screen overlap on the navigation stack before dispose fires), the factory for the second instance overwrites the global, and the first instance's deferred callback mounts to the wrong container. Conversely, if dispose on the departing widget runs after the new widget's factory has already written the reference, _cardElementDiv = null at Line 197 wipes the freshly valid reference, causing the new instance's inner callback to silently return without mounting (the null-guard at Line 91 swallows the failure).

The pre-existing #card-element CSS selector carried the same single-instance constraint (duplicate IDs), but the global-variable approach is more fragile because the null-out and the factory write are not co-located and can interleave across lifecycle calls.

Recommended fix – promote _cardElementDiv to an instance field and use a per-instance view type key so the factory closure can capture the correct state:

♻️ Proposed refactor
// Remove the top-level variable:
- web.HTMLDivElement? _cardElementDiv;

In WebStripeCardState:

 class WebStripeCardState extends State<WebCardField> with CardFieldContext {
   CardEditController get controller => widget.controller;
+  web.HTMLDivElement? _cardElementDiv;   // instance-scoped
+  // Unique per-instance viewType avoids factory-collision between instances.
+  String get _viewType => 'stripe_card_${hashCode}';

   `@override`
   void initState() {
     ui.platformViewRegistry.registerViewFactory(
-      'stripe_card',
-      (int viewId) {
-        final element = web.HTMLDivElement()..style.border = 'none';
-        _cardElementDiv = element;
-        return element;
-      },
+      _viewType,
+      (int viewId) {
+        final element = web.HTMLDivElement()..style.border = 'none';
+        _cardElementDiv = element;
+        return element;
+      },
     );
-   child: const HtmlElementView(viewType: 'stripe_card'),
+   child: HtmlElementView(viewType: _viewType),
   `@override`
   void dispose() {
     detachController(controller);
     element?.unmount();
-    _cardElementDiv = null;
+    _cardElementDiv = null;      // clears instance field only
     super.dispose();
   }

Also applies to: 61-65, 90-93, 197-197

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stripe_web/lib/src/widgets/card_field.dart` at line 14, The
top-level variable _cardElementDiv is shared across WebStripeCardState instances
and races between the HtmlElementView factory, initStripe's post-frame callback,
and dispose; change _cardElementDiv from a file-scope variable into an instance
field on WebStripeCardState so each state owns its own HTMLDivElement, update
the HtmlElementView factory to use a unique per-instance viewType key that the
factory closure captures (so it writes the correct instance field), and adjust
initStripe (the post-frame callback) and dispose to reference the instance field
instead of the top-level one to avoid cross-instance clobbering or nulling.

Comment on lines +90 to +93
final container = _cardElementDiv;
if (container == null) {
return;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent failure on null container — add a diagnostic log

When _cardElementDiv is null (e.g., due to the race described above, or a frame-ordering edge case), the early return leaves the card field blank with no indication of failure. The file already imports dart:developer for exactly this purpose.

 final container = _cardElementDiv;
 if (container == null) {
+  dev.log(
+    'WebCardField: card container element is null; Stripe card element not mounted.',
+    name: 'stripe_web',
+    level: 900, // Level.WARNING
+  );
   return;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final container = _cardElementDiv;
if (container == null) {
return;
}
final container = _cardElementDiv;
if (container == null) {
dev.log(
'WebCardField: card container element is null; Stripe card element not mounted.',
name: 'stripe_web',
level: 900, // Level.WARNING
);
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stripe_web/lib/src/widgets/card_field.dart` around lines 90 - 93,
When the early-return checks _cardElementDiv for null (in the CardField widget
where _cardElementDiv is referenced), add a diagnostic log using the existing
dart:developer import instead of silently returning; update the conditional in
the method that contains the snippet (search for _cardElementDiv and the
surrounding code in CardField/State) to call log() (or log with a level/message)
describing that the container is null and why this might occur before returning,
so callers/debuggers see a clear diagnostic when the element is missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments