Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

mikefolsom's avatar

Livewire 3 Upgrade: JS Getting Overwritten on Re-Render

Hi Laracasters,

I am upgrading a long-running app to Livewire 3 and can’t figure out why my Stripe Element keeps getting wiped when Livewire re-renders. This code worked fine in Livewire 2... 🤔

I am including a Blade component stripe-elements.blade.php inside a Livewire event registration component. The Stripe Element renders as expected on page load (i.e. I can see the input for card number, etc.) but when the Livewire component re-renders, the Stripe element gets wiped out and I see “This text will be replaced by the Stripe card element.” (which I just inserted for debugging purposes).

I have tried wire:ignore several different places but it has not solved the issue. (The code below has wire:ignore on the Stripe Element’s containing <div> which is how I had it working in Livewire 2.)

And the d-none class (CSS display:none) is not the culprit, either. I tried without ever hiding that DIV and it still doesn’t work.

TIA, Mike

Excerpt from the Livewire component:

{{-- Don’t display Stripe Element unless paying with a card --}}
<div class="{{ $this->payment_method == 'card' ? '' : 'd-none' }}">
  <div>
    <x-stripe-elements />
  </div>
</div>

stripe-elements.blade.php

<div x-data
  x-on:generate-stripe-token.window="generateStripeToken()">
  <label for="card">
    Credit Card Information<b>•</b>
  </label>

  <div id="card-element" wire:ignore>
    <!-- A Stripe Element will be inserted here. -->
    This text will be replaced by the Stripe card element.
  </div>

  <div class="smaller text-danger mt-2" id="card-errors">
    <!-- Card errors inserted here -->
  </div>
</div>

@push('scripts.body')
  <script>
    // Create a Stripe client.
    let stripe = Stripe('{{ config('services.stripe.key') }}');

    // Create an instance of Elements.
    let elements = stripe.elements();

    // Custom styling can be passed to options when creating an Element.
    let style = {};

    // Create an instance of the card Element.
    let card = elements.create('card', {
      style: style
    });

    // Add an instance of the card Element into the `card-element` <div>.
    card.mount('#card-element');
    console.log('Card element mounted.');

    // Handle real-time validation errors from the card Element.
    card.addEventListener('change', function(event) {
      let displayError = document.getElementById('card-errors');
      if (event.error) {
        displayError.textContent = event.error.message;
      } else {
        displayError.textContent = '';
      }
      console.log('Card element changed.');
    });

    // Handle form submission.
    function generateStripeToken() {
      console.log('Generating Stripe token.');
      stripe.createToken(card).then(function(result) {
        console.log(result);
        if (result.error) {
          // Inform the user if there was an error.
          let errorElement = document.getElementById('card-errors');
          errorElement.textContent = result.error.message;
        } else {
          // Send the token to your server.
          @this.set('stripe_token', result.token.id);
          Livewire.emit('stripeTokenReceived');
        }
      });
    }
  </script>
@endpush
0 likes
4 replies
LaryAI's avatar
Level 58

It seems like the issue you're facing is due to Livewire's DOM diffing engine. When Livewire re-renders the component, it updates the DOM based on the changes, which can cause JavaScript-initialized elements like Stripe Elements to be lost if they are within the part of the DOM being updated.

To solve this, you can use Livewire's wire:ignore directive to tell Livewire to ignore changes to the Stripe Element's container during DOM diffing. However, since you've mentioned that wire:ignore hasn't solved the issue, you might need to reinitialize the Stripe Element after Livewire updates the DOM.

Here's a solution that uses Livewire's wire:ignore directive along with Alpine.js to reinitialize the Stripe Element after Livewire updates:

  1. Add wire:ignore to the container of the Stripe Element to prevent Livewire from modifying it during updates.
  2. Use Alpine.js to detect when Livewire has finished re-rendering and then reinitialize the Stripe Element.

Here's how you can modify your stripe-elements.blade.php:

<div x-data="{ stripeLoaded: false }"
     x-init="$watch('stripeLoaded', value => {
         if (value) {
             initializeStripe();
         }
     })"
     x-on:livewire-load.window="stripeLoaded = true"
     wire:ignore
>
  <label for="card">
    Credit Card Information<b>•</b>
  </label>

  <div id="card-element">
    <!-- A Stripe Element will be inserted here. -->
    This text will be replaced by the Stripe card element.
  </div>

  <div class="smaller text-danger mt-2" id="card-errors">
    <!-- Card errors inserted here -->
  </div>
</div>

@push('scripts.body')
  <script>
    function initializeStripe() {
      // Create a Stripe client.
      let stripe = Stripe('{{ config('services.stripe.key') }}');

      // Create an instance of Elements.
      let elements = stripe.elements();

      // Custom styling can be passed to options when creating an Element.
      let style = {};

      // Create an instance of the card Element.
      let card = elements.create('card', { style: style });

      // Add an instance of the card Element into the `card-element` <div>.
      card.mount('#card-element');
      console.log('Card element mounted.');

      // Handle real-time validation errors from the card Element.
      card.addEventListener('change', function(event) {
        let displayError = document.getElementById('card-errors');
        if (event.error) {
          displayError.textContent = event.error.message;
        } else {
          displayError.textContent = '';
        }
        console.log('Card element changed.');
      });
    }

    // Handle form submission.
    function generateStripeToken() {
      console.log('Generating Stripe token.');
      stripe.createToken(card).then(function(result) {
        console.log(result);
        if (result.error) {
          // Inform the user if there was an error.
          let errorElement = document.getElementById('card-errors');
          errorElement.textContent = result.error.message;
        } else {
          // Send the token to your server.
          @this.set('stripe_token', result.token.id);
          Livewire.emit('stripeTokenReceived');
        }
      });
    }

    document.addEventListener('livewire:load', () => {
      initializeStripe();
    });
  </script>
@endpush

In this solution, we're using Alpine.js to watch for a stripeLoaded property change. When Livewire finishes loading, we set stripeLoaded to true, which triggers the initializeStripe function to re-mount the Stripe Element.

Additionally, we've added a livewire:load event listener to call initializeStripe when the Livewire component is first loaded.

This approach ensures that the Stripe Element is properly initialized after Livewire updates without being overwritten by Livewire's DOM diffing.

1 like
mikefolsom's avatar
mikefolsom
OP
Best Answer
Level 21

@valentinmorice Yes. Here is my working solution, with help from this post:

<div wire:ignore
  x-data="{
      stripe: null,
      card: null,
      style: {},
      elements: null,
      {{-- Invoked when Livewire dispatches a `generate-stripe-token` event (on form submission). --}}
      generateStripeToken() {
          console.log('Generating Stripe token.');
          this.stripe.createToken(this.card).then(function(result) {
              console.log(result);
              if (result.error) {
                  {{-- Inform the user if there was an error. --}}
                  $refs.cardErrors.textContent = result.error.message;
              } else {
                  {{-- Update the component. --}}
                  $wire.stripe_token = result.token.id;
                  {{-- Livewire component will listen for this event, then continue to process form/payment. --}}
                  $dispatch('stripeTokenReceived');
              }
          });
      }
  }"
  {{-- Livewire will dispatch a `generate-stripe-token` event when form is submitted with payment method == 'card'. Further form processing is stopped until a token is received and passed back to the component. --}}
  x-on:generate-stripe-token.window="generateStripeToken()"
  x-init="stripe = Stripe('{{ config('services.stripe.key') }}');
  elements = stripe.elements();
  card = elements.create('card', {
      style: style
  });
  card.mount($refs.cardEelement);
  {{-- Listen for real-time changes to the card element. --}}
  card.addEventListener('change', function(event) {
      if (event.error) {
          $refs.cardErrors.textContent = event.error.message;
      } else {
          $refs.cardErrors.textContent = '';
      }
  });">
  <label>
    Credit Card Information<b>•</b>
  </label>

  <div x-ref="cardEelement">
    <!-- Stripe Element inserted here. -->
  </div>

  <div class="smaller text-danger mt-2"
    x-ref="cardErrors">
    <!-- Card errors inserted here -->
  </div>
</div>

Essentially, it just removes the <script> injection and handles everything inside Alpine.

HTH, Mike

1 like

Please or to participate in this conversation.