Hasan's Journal

Stories, lessons, and scars from production.

Mehedi Hasan
Back to blog

Case study: react-formesh — solving the ERP form re-render problem

Same edit — updating item price inside a "line items" section — but the outcome differs completely depending on how the form state is wired. That's the diagram above, and it's the whole reason ForMesh exists.

#DNS#WebDev#DevOps#Architecture

React context vs Formesh (structural sharing)

compare-with-regular-state-manegement
compare-with-regular-state-manegement

Context: ONE ERP is an 8-module cloud platform for garment and apparel manufacturing — AIS, HCM, PLM, SCM, Budget, PMS, QMS, IE. Its forms aren't simple contact forms; they're multi-section documents (vendor details, line items, tax rules, approval routing) where a dozen fields can be open on screen at once. That's the environment ForMesh was pulled out of.

The problem with the obvious solution

React Context is the textbook answer for "state multiple components need to share." It's also the wrong answer for large forms, and the reason is structural, not a tuning problem: every consumer of a context re-renders when the context value changes, full stop. There's no way to subscribe to "just this field" — the subscription model is all-or-nothing.

For a small form, that's invisible. For an ERP-scale form with 30+ fields across multiple sections, it means every keystroke in the vendor-email field re-renders the line-items table, the tax section, and everything else on the page. The bug doesn't show up as a crash — it shows up as input lag that gets worse as the form grows, which is exactly the kind of problem that's easy to ignore until it isn't.

The fix: subscribe to branches, not the whole tree

ForMesh is built on useSyncExternalStore with structural sharing: when a field updates, only the branch of the store containing that field gets cloned. Everything else keeps its old object reference. Since React bindings compare by reference, a field subscribed to an untouched branch simply doesn't re-render — not because it's been optimized, but because nothing about its data actually changed.

That's the right-hand side of the diagram: editing item price clones the line items branch, so item qty and item total update along with it (three fields sharing one section can't be split further without hurting ergonomics), but vendor info never sees a re-render, because from React's point of view, it never changed.

How it's used

javascript code
createFormStore   →  framework-agnostic core
useForm           →  React bindings
registerField      →  wires an input into the store
{ section: "..." } →  scopes a field or subscriber to one branch

Validators are plain functions — (value, context) => message | undefined — with common ones shipped under react-formesh/validators, so there's no schema DSL to learn on top of the state model. useDebouncedSync and useWatch handle the two patterns that show up constantly in ERP forms: debounced autosave, and derived fields (a line total that recalculates when quantity or price changes) without wiring up manual useEffect chains.

What it costs

  • ~3.4 KB gzip for the core store
  • ~3.9 KB gzip including the React bindings

Small enough that adding it isn't a size conversation.

Engineering standard

  • Built with tsup, tested with Vitest
  • CI runs lint, tsc --noEmit --strict, tests, and build across the workspace
  • Versioned with Changesets, developed as a pnpm workspace (packages/formesh)
  • Apache-2.0 licensed
  • Shipped: core store, sections, useForm, field registration, debounced sync/watch, validation, file uploads, field arrays
  • In progress: TypeScript path autocomplete, a FormDebugger

Why this isn't just an ERP-specific fix

The pattern — sections that need to be independently addressable, fields that cascade, forms too large for a single flat state tree — isn't unique to ERP. CRMs, checkout flows, onboarding wizards, and admin dashboards hit the same wall. ForMesh is currently in active development and not yet on the public npm registry, but the core problem it solves (re-render blast radius scaling with form size) is one most teams building complex forms eventually run into, whether or not they've named it yet.