Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

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 ("best friends forever").

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!

12
Login or Register to join the conversation
Mathew Avatar
Mathew Avatar Mathew | posted 1 year ago | edited

If anyone is getting the warning about reactDom not being supported by react 18.
Change your made-with-love_controller.js to this.

import { Controller } from 'stimulus'
import { createRoot } from 'react-dom/client';
import React from 'react';
import MadeWithLove from "../components/madeWithLove";

export default class extends Controller {
    connect() {
        createRoot(this.element).render(
            <MadeWithLove />
        )
    }
}
3 Reply

Thanks for sharing it with others Mathew

Reply
Waldifubu Avatar
Waldifubu Avatar Waldifubu | posted 4 months ago

Maybe helpful:

import {Controller} from '@hotwired/stimulus';
import MadeWithLove from "../components/MadeWithLove";
import React from "react";
import ReactDOM from 'react-dom/client';

export default class extends Controller {
    connect() {
        ReactDOM.createRoot(this.element).render(<MadeWithLove/>)
    }
}
2 Reply

hello! is there any possible way to use React, Stimulus and symfony in the same app? I followed what you did in this episode and i rendered a React component, but when i tried to send data from react to php like submitting a form i couldn't find a way. In my app, i want to build an admin dashboard with side bar buttons that do not refresh the page, in those pages i want to display some data from the database, and each page contains a form that can add data into the database and i want to build those forms and submit and validate them with symfony forms, is there any way to communicate react with symfony through stimulus or something else?

Reply

Hey Wajdi-B!

Yes! Actually, it's a bit easier now thanks to the UX React component - https://ux.symfony.com/react - which allows you to really easily render React components and pass server data to React as "props".

I followed what you did in this episode and i rendered a React component, but when i tried to send data from react to php like submitting a form i couldn't find a way.

This makes me wonder, however, if you have a different question. So, in React you want to submit a form to your Symfony backend? Are these forms built with React? Or, what is React's role with these forms?

Cheers!

Reply
SamuelVicent Avatar
SamuelVicent Avatar SamuelVicent | posted 2 years ago

Hi! Would be possible to create a video with an example for Vue?

Reply

Hey SamuelVicent

Have you watched this video where we set up Vue on a Symfony app? https://symfonycasts.com/sc...
It's quite similar as installing React. Let me know if it helped

Cheers!

Reply

How to insert without copy paste from symfonycasts Heart emoji ?
Thanks in advance

Reply

Hey Mepcuk

You can copy paste it from Google =) But it completely depends on your OS. If you share what OS version do you use, then maybe we will be able to help you

Cheers!

Reply

IIRC emoji picker is available with ctrl + . shortcut

Hope it will help))

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial works perfectly with Stimulus 3!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "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.18.5
        "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
    }
}
userVoice