Unraveling a React App

Hey all!

I work on a SPA written in React at work. It’s got the usual issues you’re likely all familiar with, and is massively over-complicated. I’m trying to do a proof-of-concept de-Reactification of a component or two, and not sure if this is a road that’s been well-trodden yet.

My current strategy is to pick a reasonably high-level component, replace its innards with a dangerouslySetInnerHtml with an html response. But going down that path, I’m realizing this inside-out strategy is probably not going to cut it. It’s hard to avoid full page reloads, and we’d wind up basically having to re-implement something like Turbo in our components.

Coming from the other direction—doing server-side rendering, and then only rendering the remaining React components we’re not replacing (e.g., navigation and whatnot) will require a lot more work. We’d probably need to create another react component to handle Redux changes and all that fun stuff.

So I’m now curious: has anyone successfully dismantled a React application in an incremental manner? Any bright ideas how one might start down such a path?

We recently refactored an SPA app with Hotwired using the StranglerFigApplication approach. This allowed us to add new page in Hotwired while incrementally replacing the old pages over time. We took it one page at a time. We made the navigation look mostly the same. Navigating the app users move between the SPA and the Hotwired pages and did not notice they were moving between the two stacks.

We remove the last bit of SPA code a few weeks ago.


We have been experimenting with ways to attempt much the same as you, with limited success so far.

I had hoped that we could just use Turbo in our React app via npm, and custom create <turbo-frame lzy> elements in JSX. This does work, but Turbo seems to make a few assumptions that cause it to break when used in an SPA. It will not render the pre-fetched result when navigating away and back to a component that renders a turbo-frame element. It also won’t render (I think) if Turbo’s JS is loaded before a turbo-frame tag has entered the DOM.

As a result, I tried creating a crude version of Turbo of my own, and that does seem to work, but I am sure it lacks features and is vulnerable to bugs that Turbo has already dealt with. The core problem seems to be that Turbo expects to be the only tool adding turbo- elements to the page. I would love to see Turbo changed to remove this assumption, as I think it would make replacing an app from the inside out feasible. We have a highly dynamic navbar in our app, so starting from the outside isn’t too appealing.

Btw for some more background… Initially we experimented with delivering CSS and JS assets from the Rails app to the SPA basically by exposing a JSON endpoint with every top level fingerprinted asset URL, and then using importmaps for non-root dependencies (libraries and other modules). In the end though we concluded we might not actually need any CSS or JS assets if we deliver CSS inline (using Github ViewComponents experimental modularized CSS branch) and implement simple interactivity such as modals and tooltips using a shared WebComponent library.

Effectively the rails app just delivers

<our-tooltip text="You see this on hover">When you hover over this</our-tooltip>

and the React app which has a WebComponent registered for that element does the heavy lifting interactivity-wise. This shared WebComponent library also gives us a path to migrate our global styles away from React / styled-components specific styling, towards vanilla CSS that both apps can share.

To tell the truth though, we haven’t gone further than experimenting.

@bessey - React works with DOM using Virtual DOM in memory, while Turbo does not use a virtual DOM. If you are attempting to mix Turbo with React on the same pages, it will likely feel buggy. When we transitioned to turbo, we did not mix turbo with the virtual DOM JS library on the same page. Instead, we completely re-wrote a page using Turbo. You can use some turbo functionality (e.g. turbo-frames) within other frameworks, but you might run into bugs.

thanks for the awesome information.