React (or Vue) ❤️ Stimulus

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

So what about React, Vue.js and other frontend frameworks? Would you ever use those with Stimulus? Or are they opposing technologies?

Single Page Apps (SPAs) vs Server-Rendered HTML

In some ways, Stimulus and frontend frameworks are opposite, competing technologies. React, Vue or any other framework, tell us to return JSON from our server and build HTML 100% in JavaScript. But Stimulus tells us to build and return HTML from our server... then add any extra behavior we need via JavaScript.

This does lead us down two different paths. Either you're going to build a pure single page application in a frontend framework or you're going to build an app that primarily returns HTML from the server.

I prefer apps that return HTML. Why? Well beyond the fact that I like PHP, Symfony and Twig, there are two big reasons. First, we've been building so-called "traditional" apps for years... and we have a ton of tools & best practices that help us get our job done faster! Think of Symfony's form system, automatic CSRF protection, tools for handling social login just to name a few.

The second reason I prefer traditional apps is that you absolutely can still get the nice, single page app experience of avoiding full page refreshes. How? With Stimulus's sister technology Turbo, which we'll talk about in the next tutorial.

So if you're motivated to build an SPA mainly to avoid full page refreshes, well, that's not actually unique to single page applications.

Anyways, if you do choose to build an application that returns HTML and uses Stimulus, is there any use for something like React or Vue? Absolutely! If you need to build a particularly complex feature or widget on your site, using React or Vue might be the best tool for the job! And when you do this, that React or Vue component will fit perfectly into Stimulus.

Activating React in Encore

So enough talk! Let's see an example by building a mini React app. To add support for react in Encore, open webpack.config.js. You should have a spot in here that says enableReactPreset(). Uncomment that.

... lines 1 - 8
Encore
... lines 10 - 64
.enableReactPreset()
... lines 66 - 72
;
... lines 74 - 76

Whenever you change webpack.config.js, you need to restart Encore. Head over to your terminal, find the tab with Encore, stop it with Ctrl+C and re-run:

yarn watch

Ah, excellent! It reminds us that we now need a new library. Copy that and paste:

yarn add @babel/preset-react@^7.0.0 --dev

That's enough to get Encore to be able to parse React files. But to actually write those files, we need two more libraries:

yarn add react react-dom --dev

Adding the React Component

Now we're ready. Here's our mission: at the bottom of any page, we have a little "made with love" footer. It's a simple example, but I want to rebuild this in React and make it so that if we click the heart, it multiplies.

If you downloaded the course code from this page, you should have a tutorial/ directory with a MadeWithLove.js React component inside. Copy that. Go to assets/. Let's create a new directory called components/ and paste the new file there.

import React from 'react';
export default class MadeWithLove extends React.Component {
constructor(props) {
super(props);
this.state = {
hearts: 1,
};
}
... lines 11 - 29
}

This simple component renders the text, manages a hearts state and increases that heart state on click.

Adding a Stimulus Controller

So... how do we render React components if we're using Stimulus? With a custom Stimulus controller of course!

In assets/controllers/, add a new file called made-with-love_controller.js. Start the same as always... by cheating! Steal the code from counter_controller... add a connect() method and console.log(), this time, a heart.

import { Controller } from 'stimulus';
export default class extends Controller {
connect() {
console.log('❤️');
}
}

Next open up templates/base.html.twig and scroll down to find the footer... here it is. Let's replace all of this with our controller. I'll keep the footer <div>... break it onto multiple lines and then add stimulus_controller('made-with-love').

... line 1
<html lang="en-US">
... lines 3 - 13
<body>
... lines 15 - 64
<div
class="footer mb-0"
{{ stimulus_controller('made-with-love') }}
>
</div>
</body>
</html>

Let's make sure that's connected. Oh, before I do that, I think I forgot to restart Encore:

yarn watch

Awesome. Head over, refresh the page and... perfect. There's our heart!

Rendering a React Component in Stimulus

With this simple setup, we can now render the React component into our element. First, we need to import a few things: we need ReactDOM from react-dom, react from react and then our component: import MadeWithLove from '../components/MadeWithLove'.

... line 1
import ReactDOM from 'react-dom';
import React from 'react';
import MadeWithLove from '../components/MadeWithLove';
... lines 5 - 14

Down in connect, say ReactDOM.render(), pass this our component - <MadeWithLove /> - and then this.element.

... lines 1 - 5
export default class extends Controller {
connect() {
ReactDOM.render(
<MadeWithLove />,
this.element
)
}
}

That's it! Go back to the page and try it. Yes! That text is coming from our React component! And if we click the heart, it multiplies!

So... yeah! Stimulus and frontend frameworks... are... oddly enough... kind of BFF's.

Want to see something cooler? I'll inspect element on the footer and... hit "Edit as HTML". Right above this, I'll hack in another <div data-controller="made-with-love">... oh, make sure that's data-controller.

When I click off to add that... boom! We get a second, independent React component! Stimulus notices the new element and instantiates a new controller, which, of course, renders a second React app. With this setup, no matter how or when an element gets onto the page, Stimulus will handle booting up the react app.

But stimulus can do even more for React or Vue: it can help us pass data directly from our server into a component as props. It can even do this for full objects! Let's see how next.

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
    }
}