Gradual migration from Twig to React with Symfony UX and Redux

Published on

Sep 20, 2023

On those difficult days when you realize that you can't recover from a boozy seminar in one night, or that you're running out of space on the office fridge to hang the next birth announcement, it may be a good idea to comfort yourself by remembering that our tools are aging faster than we are.

In the interests of our customer, who wants to homogenize its front-end stacks, and of our developers, who are tired of being deprived of a modern framework, we have launched a migration from jQuery to React on a 7-year-old project.

As the migration had to be a long-term one, the option of going straight to a SPA (Single Page Application) was quickly ruled out: it wouldn't cohabit well with the legacy front-end, and the back-and-forth between the old and new front-ends would result in a loss of state and the obligation to completely re-render the application.

Symfony, with its Symfony UX suite, makes it easy to integrate React components into a Twig page. This allows us to move forward more freely with the migration, with Twig/jQuery and React cohabiting on the same page, while keeping the same infrastructure.

Let's take a closer look at this solution.

Behind Symfony UX lies Stimulus. It's how we're going to trigger the rendering of our React components within our Twig pages.

This is how the framework is presented in its official documentation:

Stimulus is a JavaScript framework with modest ambitions. Unlike other front-end frameworks, Stimulus is designed to enhance static or server-rendered HTML—the “HTML you already have”—by connecting JavaScript objects to elements on the page using simple annotations.

Stimulus was not initially designed to work with React. Even a cursory glance at its documentation reveals a certain disdain for the dominant javascript frameworks. For example, it states:

If you’re happy with the complexity and effort it takes to maintain an application within the maelstrom of, say, React + Redux, then Turbo + Stimulus will not appeal to you.

I'm actually very interested. And the way in which Stimulus has been hijacked to facilitate the use of its nemesis amuses me to no end!

Without going into too much detail, Symfony UX uses Stimulus to trigger React renderings on pre-selected DOM elements. This comes with a few constraints:

  • You can call up several React components in the same page. But they will act as separate mini-apps, each time a React "runtime" is relaunched.
  • It's possible to use it in conjunction with Turbo to get even closer to the feel of an SPA, avoiding the need to reload pages completely when browsing. However, React components will still be destroyed and then rerendered, resulting in flickering and loss of local states.

As it turns out, there's a simple solution to both these problems. Can you see it coming?


Indeed, it's not a case we often come across when using it with a SPA, but a Redux store can be connected to several React applications living on the same page, without any specific manipulation.

It will also enable components to recover their state during a rerender due to ajax navigation: the redux store is not destroyed when a React application is unmounted.

The circle is complete, the maelstrom is launched. With the addition of Redux, the main limitations we had identified on migration via Symfony UX React are erased, and we're off and running.

There was one point we couldn't resolve, and that was the flickering of React components during navigation.

For us, this was an acceptable inconvenience given all the advantages we gain by migrating in this way. Especially since it is possible to limit it using turbo frames.

So far, we're very happy with the comfort and freedom this approach offers. Once the migration is well advanced, we'll have the option of switching to a SPA easily if we want to.

Thank you Stimulus!

Written by

Louis Lebrault
Louis Lebrault

Self taught, Louis is now an experienced web developer. He discovered his passion for functional programming languages, especially Haskell.