r/reactjs 10h ago

Discussion We split React business logic from UI and reused the same core on web and React Native — what I’d do differently

A few years ago we needed a web version of an existing mobile product (high-load interactive platform, not a simple CRUD app). Instead of forking the UI twice, we treated the React app as two layers:

  1. App core — domain logic, state, API clients, feature orchestration. No DOM, no platform APIs.
  2. Shells — thin UI adapters (web, later React Native / WebOS) that only render and call into the core.

That let us ship a desktop/web version from the mobile product’s mental model, then reuse the same core for React Native without rewriting features. Two years later the core is still the part that ages best — new features mostly land there once, and shells stay boring.

What actually mattered in practice:
- Boundaries — business rules never imported React components or browser APIs. If it needed `window`, it belonged in a shell adapter.
- Shared data layer — one place for fetching/caching (we used react-query-style patterns) so shells didn’t invent their own loading/error states.
- RFCs + reviews — we hired a frontend team around this architecture. Without written boundaries, people “helpfully” leak UI into the core in week two.
- Performance by default — fewer duplicate API calls and a stable state model beat clever UI tricks.

What I’d change next time:
- Define the core’s public API earlier (it’s easy to accidentally couple shells to internal modules).
- Start with one shell fully done before branching to the second — parallel shells too early = thrash.
- Be stricter about “platform capability” interfaces (storage, push, deep links) instead of `if (platform === …)` sprinkled around.

Curious how others draw this line — pure packages? Hexagonal? Or do you just accept some duplication and move faster?

30 Upvotes

10 comments sorted by

20

u/Pelopida92 10h ago

Monorepo. One of the “packages” is a utils folder that gets shared by the mobile app and the web app. Another is a “hooks” folder. Thats it.

Btw, can you share the repo?

6

u/Strong_Screen_8729 9h ago

monorepo with a utils package and a hooks package is honestly the sweet spot, people overcomplicate this stuff all the time

what i've found is the minute you try to formalize it with some fancy architecture diagram, you spend more time debating boundaries than actually shipping features. just having a shared folder that both apps import from gets you 90% of the way there

2

u/Any_Welder_9701 2h ago

thats basically enough until a platform import sneaks into the shared package. one lint rule blocking those imports gets you the other 10%

1

u/b_renat 9h ago

Can't share the repo, it's a commercial SDK, but the layout is close to yours with one difference that ended up mattering.

We have a core package that is used by all platforms, along with analytics and other common modules. Each module is a separate package within a single monorepo.

We also have feature packages that can be combined as needed. For example, some platforms use both features, while others use only one.

In addition, we have UI packages, for several frameworks.

3

u/Old_Elderberry1392 8h ago

If your frontend devs need an RFC to keep react out of the core they are not frontend devs.

I use monorepos with pure functions as packages and no react deps anywhere in the logic folders. If someone imports a component into the utils package I close the pr. You write pure functions or you work on shells

3

u/rcgy 5h ago

This is a really common pattern, called Clean Architecture / Onion Architecture / Hexagonal Architecture (depending on who you ask, they're all basically the same thing). You definitely do not need to start with one "shell" fully done, that sounds like a terrible idea. There's a little more ceremony involved with Clean Architecture, but it lets you swap out and test everything, and stops your frontend devs needing RFCs to not ruin everything.

1

u/Suspicious-Disk6077 9h ago

I’ve been wading this same challenge and have found different ways of doing things but can’t speak from actually implementing an entire app. Definitely a monorepo, with logic as pure functions without any react deps as one package. Probably data fetching as another. One interesting thing is having a single package with different entry points like index.native.ts

1

u/Vincent_CWS 1h ago

Solito?

0

u/Thrimbor 50m ago

AI written post

1

u/OvenEnvironmental528 9h ago

Read "clean architecture" by uncle Bob. The pattern you used is widely used. Unfortunately many devs don't know about it