Laravel Livewire und Flatpickr: using a wire:key trick to change options

How to deal with changes for your Flatpickr configuration (or any other external js library) when updating other values in your component which would affect change that configuration.

Updated on 2021-08-27 by Dimitri König

Use case: A customer makes some changes in his shop cart, like changing the delivery type, changing quantity of articles in his cart, etc... Those changes may affect the available days to pick for a delivery wish date, which the customer can choose. Additionally all those changes may affect the earliest possible delivery date.

Using Alpinejs Caleb has written some instructions on how to make a Datepicker work with Laravel Livewire. But the focus is only on making sure the datepicker is initialized and loads the existing value correctly, and syncs back any changes made for the date.

In my case I'm using Flatpickr which can be initialized with two params: one for the target input, and the second one with options. Here comes a Laravel Blade template example:

@php
    $options = [
        'minDate' => $minDate,
        'maxDate' => $maxDate,
        'disable' => $disabledDays,
    ];
@endphp

<div data-options=""
     x-data
     x-init="flatpickr($refs.input, JSON.parse($el.dataset.options));"
>
    <input type="text"
           wire:model="desireddate"
           x-ref="input"
    >
</div>

The issue is: once Alpine initializes Flatpickr, any changes to the configuration are ignored, since Alpine does not know about any need to reinitialize Flatpickr. So how can we handle config option changes like disabled days, first available date, max. selectable date?

First Possible Solution: Watcher to config changes

You could setup a watcher for config changes, and then call flatpickr.set(configKey, config) to modify the configuration. You would need to make sure that even the config "disable" needs to be changed if the content changed thought the key length did not change, a common theme in such watcher topics.

Second Possible Solution: Use a wire:key hack to your advantage

wire:key is needed (not only but also) in case of issues due to DOM changes from external libraries, see the dedicated Troubleshooting page on Livewire Docs.

You could add a wire:key attribute with an always changing value to the div tag around, which makes Alpinejs run the init function every single time:

@php
    $options = [
        'minDate' => $minDate,
        'maxDate' => $maxDate,
        'disable' => $disabledDays,
    ];
@endphp

<div data-options=""
     x-data
     x-init="flatpickr($refs.input, JSON.parse($el.dataset.options));"
	 wire:key="datepicker-"
>
    <input type="text"
           wire:model="desireddate"
           x-ref="input"
    >
</div>

That way anytime Livewire updates that component, Alpine thinks that surrounding div is new and runs the x-init method again.

But now we have two more issues to solve:

  1. How to make sure x-init and reinitialization of Flatpickr is only run on config changes and not any time the whole component gets updated?

  2. What about destroying previous instances of Flatpickr before an update, to make sure we don't have any loose ends in terms of memory consumption?

Second and a half Possible Solution: Use a wire:key hack with a config options hash to your advantage

For the first point we could modify our wire:key content to use a hash of the config options. That way any time a Livewire update occurs but no config options got changed, Alpinejs does not run the x-init method again.

@php
    $options = json_encode([
        'minDate' => $minDate,
        'maxDate' => $maxDate,
        'disable' => $disabledDays,
    ]);
@endphp

<div data-options="{{ $options }}"
     x-data
     x-init="flatpickr($refs.input, JSON.parse($el.dataset.options));"
	 wire:key="datepicker-"
>
    <input type="text"
           wire:model="desireddate"
           x-ref="input"
    >
</div>

For the second option you could use an undocumented feature of Alpinejs 3, which is nicely described here: Alpine 3.x Tips and Tricks - 2. Clean up after yourself with destroy.

But that solution does not work with Alpine 2.

Here we can make a calculated tradeoff: How often does a customer make any changes which could affect a change in the datepicker config options? In my case: it happens but rarely. So let's leave it as it is without making that last issue way more complex than it should be.