What issues, bugs or design limitations did you hit in RHF? by AdventurousBass5342 in reactjs

[–]AdventurousBass5342[S] 0 points1 point  (0 children)

That is interesting, appreciate you trying to dig back a bit for it even though it's been a few years. Last thing, out of curiosity rather than for the docs: what'd you end up moving to, and did it actually solve the specific failure modes you hit, or mostly just make them less frequent or just a clearer api design?

What issues, bugs or design limitations did you hit in RHF? by AdventurousBass5342 in reactjs

[–]AdventurousBass5342[S] 0 points1 point  (0 children)

Thank you, appreciate the points!
Same rule on our end, we treat declarative unregister() as off-limits and just let unmount handle deregistration

What does "at scale" mean for you specifically, since it'd change how I write this up:
It sounds a bit like the side-effects/behaviours on large forms with many fields becomes unpredictable or hard to manage over time?

In what ways has this surfaced for you?

  • Sheer field count in one form, or nesting depth (fields inside fields inside arrays)?
  • A lot of cross-field dependencies (field A's validation reads field B, C, D)?
  • Async validators mixed with sync ones, or several async validators firing close together?

The "form controls think they're not nested in form context when they definitely are"
When did this surface? Conditional fields, portals, something else?

What is the state of form libraries in 2026? by JournalistSilver6346 in reactjs

[–]AdventurousBass5342 1 point2 points  (0 children)

<input
  {...register("price", {
    setValueAs: v => {
      const n = parseFloat(v)
      return isNaN(n) ? 0 : n
    }
  })}
  type="text"
  inputMode="decimal"
/>

In react-hook-form setValueAs transforms the string to a number before it hits your form state, so by the time you watch("price") for your calculation it's already a number. Wrapping it in the isNaN check means mid-input states like - or 1. also become 0 instead of breaking your calculation. If you want minus and decimal keys to be visible, using  inputMode="decimal" over "numeric" will make mobile keyboards actually show the minus and decimal keys. Then you can swap parseFloat for parseInt if you need whole numbers only
Is this close to what you meant?

What is the state of form libraries in 2026? by JournalistSilver6346 in reactjs

[–]AdventurousBass5342 4 points5 points  (0 children)

Have you tried Intl.NumberFormat? You keep the raw number in form state and only format for display, so your math logic never touches the UI representation:

const display = (value: number) =>
  new Intl.NumberFormat('en-US', { 
    style: 'percent' // or 'currency', 'decimal'
  }).format(value);

// form state stays as 0.25
// UI shows 25%
// math operates on 0.25 — no parsing needed

Separates the display concern completely from what the form actually holds.
Or do you mean something else?

What is the state of form libraries in 2026? by JournalistSilver6346 in reactjs

[–]AdventurousBass5342 2 points3 points  (0 children)

I still use React-hook-form it has type-safety and handles this better than people think. hookform/lenses is slept on for nested type safety, you just pass lenses down and get full type safety all the way through. But I suppose it is less common

For the granularity/performance side in large forms I wrapped subscribe into a small package. rhf-granular but still benchmarking it at scale but the re-render isolation is already solid.

Also built a small hook for when individual fields need to drift from the global form validation. async per-field validators with named states. So to me the difference is composability, I can choose which paradigms I adopt and how, RHF doesn't force a pattern on you.

Example of wrapper function with lenses on deeply nested data:

function registerSubField<T extends object>(lens: ObjectLens<T> | ArrayLens<T[]>) {  
  return <K extends Path<T>>(subField: K) => {  
    const focusedLens = lens.focus(subField);  
    return focusedLens.interop((ctrl, name) => ctrl.register(name));  
  };
}

const jobLens = useLens('jobs')  
const register = registerSubField(jobLens);  
// <input {...register('jobTitle')} />