useTransition in a Neat, Reusable Module

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We need to initialize the useTransition() behavior on this controller so we can call this.enter() or this.leave() inside toggle to show or hide the results.

This time, instead of putting all the code right here, let's create a reusable function so that we can quickly add our fade transition behavior to other controllers in the future.

Creating addFadeTransition()

In the assets/ directory, add a new directory called, how about, util/. And inside that a new file called add-transition.js. I'll paste in the code: you can get this from the code block on this page.

import { useTransition } from 'stimulus-use';
export function addFadeTransition(controller, element) {
useTransition(controller, {
element,
enterActive: 'fade-enter-active',
enterFrom: 'fade-enter-from',
enterTo: 'fade-enter-to',
leaveActive: 'fade-leave-active',
leaveFrom: 'fade-leave-from',
leaveTo: 'fade-leave-to',
hiddenClass: 'd-none',
});
}

This exports a named function called addFadeTransition() which adds the useTransition behavior to the passed controller. Most of what you see here is identical to what we had when we originally leveraged useTransition.

Setting up the Transition Behavior

Cool! Back in our controller, in the connect() method, which is normally where we add behaviors, say addFadeTransition and hit tab to autocomplete this... so that PhpStorm adds the import for us! Nice! Pass the controller - which is this - and then we need to pass the element that is going to be hidden or shown... which we don't actually have access to yet. Let's add a target for that. Pass this.resultsTarget... then initialize that target above: static targets = an array with results inside.

... line 1
import { addFadeTransition } from '../util/add-transition';
... line 3
export default class extends Controller {
static targets = ['results'];
connect() {
addFadeTransition(this, this.resultsTarget);
}
... lines 10 - 13
}

In the template, we need to add the target to the results div. Hmm, this already has a target for the autocomplete controller. Copy that, paste, and add an identical target for our autocomplete-transition controller.

... lines 1 - 2
{% block body %}
... lines 4 - 37
<form>
... lines 39 - 57
<div
... lines 59 - 60
data-autocomplete-transition-target="results"
></div>
... line 63
</form>
... lines 65 - 114
{% endblock %}

It is a bit weird to have two different targets on the same element... but this is totally allowed. If you really didn't like this, you could actually find this element manually in your controller by using the this.element.querySelector() to find this element using the data-autocomplete-target attribute.

Anyways, back in our toggle method, because we've initialized the useTransition behavior, we now have enter() and leave() methods. And so, if event.detail.action equals open, call this.enter(). Else, call this.leave().

... lines 1 - 3
export default class extends Controller {
... lines 5 - 10
toggle(event) {
if (event.detail.action === 'open') {
this.enter();
} else {
this.leave();
}
}
}

Let's try it! Move over, refresh, type "de" and... yes! It transitioned!

As a reminder, the details of this transition - like the fact that it fades for 2 seconds - live in the app.css file. Search for "fade": here they are.

By the way, that 2000 milliseconds is way too long: I'm only using that so the transition is obvious.

The skipHiddenProperty Value

Anyways, back at the browser, type to re-open the suggestions then click off of to close it. That happened instantly! Where was our transition?

Inspect the element. Ah: see that little hidden attribute on the results div? That was added by the stimulus-autocomplete controller as soon as we clicked off. Thanks that, the element became hidden instantly instead of waiting for our transition.

Normally, that's great! It's how stimulus-autocomplete hides the results. But now that we are controlling the hiding and showing with our transition behavior, we do not want this hidden attribute to be added. Fortunately, assuming my PR is merged, we can pass a value to disable that behavior.

In the template, on the autocomplete controller, pass a new value called skipHiddenProperty set to true.

... lines 1 - 2
{% block body %}
... lines 4 - 37
<form>
<div
... line 40
{{ stimulus_controller({
'autocomplete': {
... line 43
skipHiddenProperty: true
},
... line 46
}) }}
... line 48
>
... lines 50 - 63
</div>
</form>
... lines 66 - 115
{% endblock %}

That literally says: please do not set that hidden property: we are handling the hiding and showing ourselves.

Let's try it out again. I'll type... we still get the nice fade in... and when I click off. Yes! It fades out!

And.... we're done! I mean, the whole tutorial is done! I hope you found this journey through Stimulus as refreshing as I did. I love coding with Stimulus.

In the next tutorial in the series - about Turbo - I hope to show prove that we can have an even more dynamic and speedy app while writing even less custom JavaScript.

Let us know what cool stuff your building. And, as always, if you have any questions, we're here for you in the comments section.

Okay, friends, seeya next time!

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=7.4.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "1.11.99.1", // 1.11.99.1
        "doctrine/annotations": "^1.0", // 1.11.1
        "doctrine/doctrine-bundle": "^2.2", // 2.2.3
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
        "doctrine/orm": "^2.8", // 2.8.1
        "phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
        "sensio/framework-extra-bundle": "^5.6", // v5.6.1
        "symfony/asset": "5.2.*", // v5.2.3
        "symfony/console": "5.2.*", // v5.2.3
        "symfony/dotenv": "5.2.*", // v5.2.3
        "symfony/flex": "^1.3.1", // v1.12.1
        "symfony/form": "5.2.*", // v5.2.3
        "symfony/framework-bundle": "5.2.*", // v5.2.3
        "symfony/property-access": "5.2.*", // v5.2.3
        "symfony/property-info": "5.2.*", // v5.2.3
        "symfony/proxy-manager-bridge": "5.2.*", // v5.2.3
        "symfony/security-bundle": "5.2.*", // v5.2.3
        "symfony/serializer": "5.2.*", // v5.2.3
        "symfony/twig-bundle": "5.2.*", // v5.2.3
        "symfony/ux-chartjs": "^1.1", // v1.2.0
        "symfony/validator": "5.2.*", // v5.2.3
        "symfony/webpack-encore-bundle": "^1.9", // v1.11.1
        "symfony/yaml": "5.2.*", // v5.2.3
        "twig/extra-bundle": "^2.12|^3.0", // v3.2.1
        "twig/intl-extra": "^3.2", // v3.2.1
        "twig/twig": "^2.12|^3.0" // v3.2.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
        "symfony/debug-bundle": "^5.2", // v5.2.3
        "symfony/maker-bundle": "^1.27", // v1.30.0
        "symfony/monolog-bundle": "^3.0", // v3.6.0
        "symfony/stopwatch": "^5.2", // v5.2.3
        "symfony/var-dumper": "^5.2", // v5.2.3
        "symfony/web-profiler-bundle": "^5.2" // v5.2.3
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@babel/preset-react": "^7.0.0", // 7.12.13
        "@popperjs/core": "^2.9.1", // 2.9.1
        "@symfony/stimulus-bridge": "^2.0.0", // 2.1.0
        "@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
        "@symfony/webpack-encore": "^1.0.0", // 1.0.4
        "bootstrap": "^5.0.0-beta2", // 5.0.0-beta2
        "core-js": "^3.0.0", // 3.8.3
        "jquery": "^3.6.0", // 3.6.0
        "react": "^17.0.1", // 17.0.1
        "react-dom": "^17.0.1", // 17.0.1
        "regenerator-runtime": "^0.13.2", // 0.13.7
        "stimulus": "^2.0.0", // 2.0.0
        "stimulus-autocomplete": "^2.0.1-phylor-6095f2a9", // 2.0.1-phylor-6095f2a9
        "stimulus-use": "^0.24.0-1", // 0.24.0-1
        "sweetalert2": "^10.13.0", // 10.14.0
        "webpack-bundle-analyzer": "^4.4.0", // 4.4.0
        "webpack-notifier": "^1.6.0" // 1.13.0
    }
}