We chose React to learn it. The AI wrote it for us.

[This article was first published on Rtask, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

You can read the original post in its original format on Rtask website by ThinkR here: We chose React to learn it. The AI wrote it for us.

Diary of a rewrite, episode 1 of 4.

In February 2026 we chose React to rebuild an internal tool, knowing full well it would take twice as long as doing it in R. It was a deliberate trade: we were not buying an application, we were buying skills.

In June, halfway through, with two milestones already delivered, we stopped everything and went back to R.

Here are the three reasons. Only one of them is “we were wrong”. The one that settled it is more uncomfortable than that: the learning we were paying a premium for had simply never happened, because the code was arriving faster than we could read it.

StaffPuzzle is our internal staffing tool: who works on what, which half-day. Version 1 is a read-only Shiny application, 1,200 lines, that has been running for years.

staffpuzzle v1

Version 2 was meant to add writing to Google Calendar, a simulation mode and historised indicators. We made the stack decision twice, four months apart, and the second one undid the first. Both are written down, with their criteria. That is what makes the story tellable, and it is also, as we will see, what made it possible.

This series covers the four months that followed, in four episodes. It ends on the real numbers: what the rewrite cost, what the hosting costs, and what the licence we avoided was worth.

19 February: choosing React

Three options on the table: all JavaScript (React, Supabase), all R (raw HTMX on raw plumber2), or a hybrid. The criteria table was explicit, and it is worth reading exactly as it was written:

Criterion Option A: React Option B: R Four months on
Learning modern frontend Strong Weak never happened
Time to market ~19-24 half-days ~12 half-days held
Reuse of existing R None Total held
Transferability to other web projects High Low wrong criterion
Rich interactivity Natural Difficult factually false

Look at the second row. We knew React would cost twice as much, and we chose it anyway. This is not naivety, it is a deliberate trade. The team is six people, it knows R and has almost no exposure to modern web stacks; version 1 works, there is no urgency. The Architecture Decision Record (ADR) says so plainly: the team’s comfort with React is “junior”, and that is “a deliberate investment, not an asset we already hold”.

In other words, the February decision was not buying an application. It was buying skills, and accepting to pay for them in delay.

Architecture Decision Record: a document that records a significant software architecture choice and the reasoning behind it.

The option that was not in the table

You may have noticed: “stay on Shiny” is not there. The three options examined were all-JavaScript, all-R on HTMX, and a hybrid. None of them meant keeping the tool as it was and adding what it lacked.

This was not an oversight. The question had been settled upstream, in the project brief written a month earlier, under the heading “Technical constraints”:

Target stack: exit Shiny, REST architecture, classic web site.

Written there, at the same level as Google API quotas and OAuth token handling. But Google’s quotas are a constraint: they are imposed on us. “Exit Shiny” is a decision. Filing a decision under constraints takes it out of the debate. You do not argue with a constraint, you work around it.

Nothing forced us off Shiny. Writing to a calendar, simulating, historising indicators: Shiny can do all of that. So the February ADR diligently compared three options inside a perimeter nobody had justified. And in June, when we reopened the file, we went back over every criterion one by one without ever questioning the perimeter.

What matters for what follows. A decision justified by learning is only valid if the learning happens. That is a hypothesis, not an asset, and it can be checked.

3 June: the U-turn

Four months later, two milestones delivered in React, the team reopens the file. Out of it comes a second decision that cancels the first. Three things had moved, and they are not of the same nature.

One: the option we had refused no longer existed

In February, the R option on the table was raw HTMX on raw plumber2. Writing the HTML by hand, setting the attributes by hand. That was a fair criticism, and it had weighed.

In the meantime, two R packages had been released, {htmxr} and {alpiner}, which give that approach the high-level R abstractions it was missing. June’s option B is not February’s option B. That is not the same thing as having been wrong: the world had changed, and the decision had no mechanism for noticing that on its own.

Two: two of the arguments were simply false

February’s table rated rich interactivity as “difficult” on the HTMX side, with drag and drop as the example. In June, someone went and read the React code that had actually been written: there is no drag and drop. It is a lasso selection, which is precisely what {alpiner} does natively. The argument was defending a difficulty that did not exist.

The second one needs more care. The criterion said: React skills are “highly transferable to other web projects”, R skills much less so. That is true, and it is the wrong criterion. It measures what a developer carries away on their CV, not what a company accumulates. For a firm whose business is R, going deeper into R is not low transferability: it is the main asset getting thicker. Maintaining packages headed for CRAN makes us more visible and more capable than a surface knowledge of React ever would have.

Three: the learning had not happened

This is the one that hurts, and it is the one that settled the decision. June’s ADR puts it in a single sentence:

Learning React, the central motivation for February’s decision, is not happening in practice: the developer does not have time to read the code generated by the AI, so the skills objective is not being met.

ADR-003, 3 June 2026

It is worth weighing what that says. The sole justification for four months of extra cost was skills. The code had been written, the application worked, the milestones were delivered. And the benefit that paid for the extra cost had not materialised.

With an AI assisting, a stack decision justified by learning can sabotage itself. The code arrives faster than you understand it, delivery moves forward, and the illusion holds until somebody asks to see what was learned. The subject goes well beyond this article and we will come back to it; here it is enough to note that it changed a technical decision halfway through a project.

What it cost: the price of the U-turn

A reversal is not free, and June’s ADR lists its own downsides before concluding.

The first one is not technical:

  • Redoing the communication with our internal users. Changing technical direction mid-project, in front of people waiting for a tool, requires an explanation you would rather not have to give.
  • Stabilising still-experimental packages in order to put them in production.
  • The bus factor: the ecosystem we chose has one main maintainer, and it is the same person as the project’s developer.
  • Salvaging the documentary value of the React code before deleting it: the types and the tests described a data model that was still valid.

The arithmetic, though, was favourable: six to eight half-days to redo everything, against eight to ten to finish in React. A proof of concept written straight afterwards, 280 lines of R reproducing the main screen at 70 milliseconds per request, turned that estimate into a commitment.

The numbers are what convinced us. They are not what decided it.

Three months on: where we are

The application runs in production, writes to the calendars, and historises its indicators. The tests-to-code ratio went from 0.26 to 0.77: 544 tests, no browser, a single language from the calendar all the way to the screen.

That is the gain we had not anticipated. The interface having become a pure function from data to HTML, it is tested like the rest of the code, with no browser and no screenshots. We justified the return by consistency of language; what we got that was most valuable is testability.

And one benefit that was in no table at all: the application became the first real production case for our own packages, the ones in the hyperverse, which no demo example would ever have given us.

staffpuzzle v2

What we take away

Writing decisions down is what lets you undo them. That is the main lesson, and it sounds banal right up until you need it. In June, nobody had to reconstruct from memory why React had been chosen: the criteria were written, dated, weighted. It was enough to go back over them one by one and see which no longer held. Without that document, the discussion would have been about people, about who had been right, instead of about criteria.

Separate “the world changed” from “we were wrong”. Of the three reasons, only one is an error of judgement: the drag-and-drop argument, which reading the code would have disproved back in February. The other two are of a different kind: a tool appeared, and a hypothesis about ourselves turned out to be false. Confusing the three produces either pointless guilt or an inability to turn back.

A decision justified by a non-technical benefit must schedule its own verification. February was buying learning. Nobody had planned to check that it was arriving. We found out by accident, four months later, reopening the file for another reason. That is the one thing we would do differently: if a criterion decides, it needs a review date.

Would we do it again. We would probably choose React again, in February 2026, with what we knew then. That is what makes the story interesting: the U-turn is not the correction of a blunder, it is how a decision behaves when you have taken the trouble to write it down.


Has your Shiny application outgrown itself, and you are wondering what to do with it? We read it and give you back a note that says so, including when the answer is “leave it alone”. Write to us in two lines: the size of your application, and how long it has been running.


Diary of a rewrite, four episodes:

  1. We chose React to learn it. The AI wrote it for us. (you are here)
  2. My first attempt at porting a Shiny app failed, and not for a technical reason (29 September 2026)
  3. We left the managed platform. Here is the real bill. (6 October 2026)
  4. Five signs your Shiny app has outgrown Shiny (13 October 2026)

The two decisions cited are Architecture Decision Records from the StaffPuzzle repository, dated 19 February and 3 June 2026. The figures are measured on the repository’s branches, not estimated. Disclosure of interest: htmxr and alpiner are written by Arthur Bréant, who led this project and maintains those packages.

This post is better presented on its original ThinkR website here: We chose React to learn it. The AI wrote it for us.

To leave a comment for the author, please follow the link and comment on their blog: Rtask.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)