← All posts

Declarative field dependencies in React forms

Replace useEffect spaghetti with dependency config, the Observer pattern, and MobX fine-grained reactivity.

Declarative field dependencies in React forms
Contents

In brief

A Dev.to write-up tackles complex React forms by describing field relationships in configuration, not chains of useEffect.

Fields subscribe to each other (Observer pattern). MobX provides fine-grained reactivity—only what actually changed re-renders.

A pharmaceutical submission prototype with dozens of fields cut complexity. Rules became readable even for teammates outside the client UI.

What happened

Enterprise forms are rarely three inputs: registration, checkout, surveys, data entry.

The recurring pain is field dependency: country reveals state, application type narrows submission types, value combinations trigger validation or warnings.

The usual React path is a useEffect per change. Fine for five fields.

With thirty cross-linked chains it becomes spaghetti: loops, brittle tests, fear of adding one more rule.

Every new field forces another pass through the effect graph so existing chains stay intact.

The author recalls an older XML-driven UI where dependencies were declarative (dependsOn, loadFrom, clearOnChange) without hand-written handlers.

Modern React declares markup, yet field wiring is often imperative. The article’s alternative is a form model array.

Each field states subscribesTo / subscribesOn, where to load options (loadDataFrom), and when to show (visibleWhen). Read top to bottom and you see the form contract.

For a team, that is also a shared language for business rules with the backend.

Making that work needs “field A changed → interested fields learn.” Naively scanning every field after each keystroke crawls on 30+ fields.

The fix is the Observer pattern plus a store with fine-grained reactivity. The author contrasts Redux/Zustand with MobX and builds functional stores without classes.

A subscription engine handles chains, cycles, and races when loads overlap—exactly where imperative effects usually break.

The author notes honestly that debugging the engine is harder than tracing one explicit useEffect. In return, the form contract stops being smeared across components.

Why it matters

Complex forms are not a pharma niche. The same patterns show up in fintech, government services, e-commerce, and internal admin tools.

Imperative effects scale poorly as documentation. A backend engineer cannot “read” business rules from a dozen useEffects.

Config-as-data is easier to review, reuse across view/edit modes, and partly generate from the server.

When rules live in data, they are easier to version with the API and unit-test without mounting the whole component tree.

There is a cost: a custom config format, learning curve, harder subscription-engine debugging. Some rules still need imperative escape hatches.

For a simple country→city dependency, library overhead may not pay off. The approach wins on large forms with repeating chains.

Approach How links are described Typical risk
useEffect chains Inside effect code Hidden loops, fragile edits
Config + subscriptions In model data Learning curve for the engine
Values library only Partly in code Relationships still manual

In practice

If your form already has “effects that trigger effects,” sketch the dependency graph first.

Often half the rules collapse into two repeated operations: show a field and refresh option lists.

Describe those two actions in config first, then decide whether you need a dedicated library or a thin layer on top of your current form stack.

  1. List the graph: which field affects what (visibility, list reload, value clear, validation rule).
  2. Extract two common actions—show/hide and refresh dependent options.
  3. For large forms, evaluate fine-grained reactivity (MobX or similar).
  4. Test subscription rules as pure data, separate from mounting React components.
  5. Do not discard Formik / React Hook Form: they excel at values and submit; declarative dependencies are about relationships.

Separately decide where the source of truth lives: client-side config only, or partly from the API. The second option pays off more when you have many similar forms.

If the team already uses Formik or React Hook Form, do not throw them out for purity—add a dependency layer on top, not instead.

Takeaway

Declarative field dependencies do not ban useEffect forever.

They move “who affects whom” out of imperative noise into readable config. On a pharmaceutical form prototype, that cut logic volume and made rules clearer to the team.

Use the pattern where complexity already hurts, not as fashion for every login screen.

If the form is still simple, keep explicit effects. Once the dependency graph no longer fits in one developer’s head, config and subscriptions pay off faster than another layer of useEffect.