Hacker News new | ask | show | jobs
by unconed 385 days ago
I've written a similar `useDerivedState` hook before, which is basically formalizing the lastValue !== value / setLastValue pattern that the docs teach you.

But there is a major blind spot. The reason you want the dependencies is to reset the state when the outside demands it. But only way you can reset such a state is if the dependency _changes_. So it's not possible to reset the state back to the _same_ value as before.

To do that, you either need to manually manage the component lifecycle with a `key={..}` on the outside, or, you need to add e.g. a `version={N}` counter as an extra prop, to handle the edge case. Except, at that point, it makes more sense to rely on `version` entirely.

The 'proper' solution I've found is to actually write code to do what the policy and etiquette demands. E.g. for a number input, you can't aggressively reformat the contents because that's unusable, but you can check if the current text parses to the same value as before (if it is a valid number). There is no way to hide this sort of nuance with a generic state hook, it's too context-specific.

What is most useful is to treat the bridge between controlled and uncontrolled state as its own wrapper component, e.g. called <BufferedInput>, which has a render prop to actually render the <input> itself. It accepts a `value / onChange` on the outside, but passes on a different `text / onChange` to the inside. Give it a `parse`, `format` and `validate` prop that take functions, and you can clean up a lot of messy input scenarios.