Show HN: React Hook Form – Simple Form Validation
react-hook-form.com
react-hook-form.com
As form code is the most important entry point for security flaws in web applications it is also the most expensive and it is not a good idea having to touch production code for every little backend change. This looks like PHP coding 15 years ago. Do not do that.
I think I have heard of something similar but it didn’t seem to have caught on in any major web frameworks that I know of (strongly typed json API server which generates runtime validators on frontend and backend). Even then most people want control over the presentation of the form and wouldn’t want to create fields from the schema (e.g. a number type is sometimes a slider and sometimes a drop down)
If I have a form anything more complex than a login page, then I usually use some kind of generator based on a schema.
A plain data format (like JSON) should be used to generate Forms, so your client/server side validation is always in sync (DRY).
But this isn't mutually exclusive. You could use this component and generate the implementation (or just plainly transform your data into this with a function). Especially since it leans very heavily on the HTML standard, which has several advantages.
Having a look at the code examples and documentation it would seem ergonomic to just feed JSON into a component that uses this.
Not only this, but I think generally when a backend change happens you want to be aware of it (and fail early, probably in the test/integration phase!) - leaving it potentially as late as runtime could be devastating.
For very basic forms generation from a model is fine, but I think it hits its limits pretty quickly. Imo generating a model from a schema is an okay halfway house.
Regardless, this tool seems pretty nice, and could save me a little boiler plate!
1) Is it just me or so hooks really push you to commit to either “state comes from props” or “state is held internally” but you REALLY can’t mix the two?
So, if I have say, a searchable select box, I must either A) be stateless, and let some other part of the system store the user’s typing, or B) use a state hook, but NEVER react to state changes coming in from outside.
I.e. I am tempted to use useEffect to notice my props and my state being out of sync so I can sync them back up, allowing either the user or the backend to provide changes. But that also feels bonkers.
Doing the same thing through message passing also seems bonkers, React was created to replace having to pass messages around to every component that needs updates, wasn’t it?
2) Is there any point to useCallback? Is it literally the same thing as returning a function from useMemo?
3) Is it just me or is useRef kind of a horrible hack? Lots of articles tell me to do myRef.current = myState in order to hack around the fact that state changes don’t take effect until the next tick. But now we no longer have a single source of truth, and introduce lots of sync bugs. Also have to type .current everywhere.
4) Should I be using useEffect instead for solving the issue with #3?
I just have this sinking feeling that at least one of useRef, useMemo, useCallback, and useEffect shouldn’t exist, but I am only three months in to React and I can’t really see the forest through the trees.
2)There are several points, but I think the most important one is simply just to save time instead of using useMemo as passing callback to child components is pretty common. And yes, it is the same. The official docs literally said that. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps). [0]
3)You can call it a hack I guess. If you don't want to wait for rerender, you can keep a value inside a ref! While class component can just create a property, function component doesn't have that. That's where the useRef came in, and that's just one use of useRef. I'm not really sure about the articles you mentioned. I never faced a use case where you would use a state and also keeping it inside a ref, can you share an example link maybe?
For the .current part, since refs is originally used to access the DOM. And the DOM node could change for whatever reason. To access the node, we use .current.
4) It depends, but I don't think keeping two things in sync using useEffect hook is a good idea. I'm not really clear about the issue, so I can't say much.
After using React for a while with class and lifecycle, I think all the available hooks is really nice. Feels like we got a fresh wind while developing with React. Using hooks and not have enough contrast to make me feel I can no longer code React without hooks. They let you reuse and share the same logic for different components or projects easily. Less code to read, and for me, functional component with hooks is way easier to read compared to a class component.
Refs: [0] https://reactjs.org/docs/hooks-reference.html#usecallback
Hooks do a good job of nudging you to make better decisions about how to design your app, primarily by making bad decisions harder to make. This feels bad initially but once you get it, I think its better.
That said, lets address your issues:
1. Yes/No. There is no reason you cannot have both props and state effect your component, however I don't think thats your question.
If you need something outside the component to effect what is typed into the box, (e.g. the app can clear the text) then yes, you should be passing the text in as a prop, along w/ an onChange callback prop for when the text is changed within the component. I think of it as, if the state can be changed somewhere else then it doesn't belong to the component, and is thus a prop. Note, the options could be filtered in the component or not, also up to you.
2. I agree with this question. The main use I've found of useCallback is to avoid triggering changes to other hooks. ¯\_(ツ)_/¯ I think react team members have even asked about how people use it: https://twitter.com/brian_d_vaughn/status/117435997560009113...
3. useRef is great. It allows you to store state which does not trigger re-renders. This gives you a finer detail control of your component. I would not use it to "hack around the fact that state changes don’t take effect until the next tick" though. I think it would be bad practice to have to think about when state changes take effect. (This might be one of the "making bad decisions harder to make" moments)
P.S. I agree about typing .current everywhere, but it makes sense from a JS perspective. useRef is simply keeping track of an object instance and you are accessing a member of that object. I'm not sure if it has to be 'current' or if thats just convention (anyone know?).
4. I can't answer this without an example. Generally, if you need the value to render, you either have it via a prop or it it is already stored in state. If you need to transform that value in order for it to look right, you do that inside your function. If it is a complicated calculation, do it with useMemo, so it only has to be done if your value changes.
If something causes your state to change, you should just update the value and then allow it to rerender, thus it becomes the already in state case. Those changes should really only be part of other actions though, not your render function itself.
So that was long winded, but hopefully useful. I think hooks are good and generally use the following heuristic: - useState: state which causes rerenders on change
- useRef: state which does not cause rerenders on change
- useMemo: calculated state based on other props/state (only needed if the calculation is expensive)
- useCallback: useMemo for functions :p
- useEffect: Do something based on state or lifecycle of component (e.g. mount/unmount)
(edited for formatting)
You can combine whatever inputs you want, whether props or internal state variables. Hooks are basically a way to specific "do X whenever any of Y variables change", regardless of what those vars are.
for example, after a form submit event, you can do
const elements = [...e.target.elements].filter(el => el.name); // all form elements with a name
// invoke onSubmit prop with form data:
onSubmit(
elements.reduce((o, el) => ({
...o,
[el.name]: el.type==='checkbox' ? el.checked : el.value
}), {})
);function App() {
const { register, handleSubmit, errors } = useForm(); // initialise the hook
const onSubmit = data => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input name="firstname" ref={register} /> {/* register an input */}
<input name="lastname" ref={register({ required: true })} />
{errors.lastname && 'Last name is required.'}
<input name="age" ref={register({ pattern: /\d+/ })} />
{errors.age && 'Please enter number for age.'}
<input type="submit" />
</form>
);
}here is an code example, there is no local state.
If you store some state in JS variables, and some in the DOM (forms), then that's not true anymore. That's not necessarily a problem if you're expecting it (uncontrolled components are a valid React paradigm) - but if you're expecting controlled components, but some of them aren't, then you can run into trouble.
Also, once you're used to controlled components, it actually feels like a hassle to keep the data in the DOM - you have to constantly be getting it from the DOM to do form validation, submitting, field resetting, etc.
Again - I would say that neither is more right or wrong; but it's more of a style choice.
Of course you can do this imperatively by manually querying the DOM and getting the input values, but I think it's nice to have the form data in the state to work with and have full control over it.
That said Forms are one thing where I don't understand all the libraries built to 'simplify' them. They are not complex, and trying to force the library to fit your use case usually ends up being more burdensome than just writing the extra 20-30 LoC.
import forn from 'forn';
export default () => (
<form onSubmit={forn(data => console.log(data))}>
<input name="firstName" placeholder="First Name" />
<input name="lastName" placeholder="Last Name" />
<button>Send!</button>
</form>
);
Test it: https://codesandbox.io/s/agitated-johnson-3hv96Why do you need the register though? Is the hook needed to hold the refs? Is it only for compatibility with React Native or are there other reasons? With forn I use `e.target`, which should not be available in RN.
1. Support React controlled component. eg some of the popular component libs, they are wrapped input inside their component and it's hard to predict which to include during the form submit.
2. React Native <3
3. Attach validation rules, messages and async validation on the individual field level.
I'll try it next time I have to deal with React Native!
Also, maybe it’s because of hooks, but it feels like less code than usual is required for forms. I once built a massive “multi page questionnaire with conditional logic” feature, using a superset of json schema and a wrapper around react-json-schema-form (the Mozilla one). That was two years ago and a LOT of code (I used a singleton controller to parse local updates and merge them into a global schema). If I had hooks and this library available then, it would have been significantly less code.
I have built a custom hook for form validation, please check it out and feedback welcome <3
It would be lovely to just yarn add something that would give me Material-UI components already set up. I'm sure others would love a Bootstrap version... etc. The more you can lower the barriers to entry, the better.
While it didn't take me that long to do, it does have some weird edge cases that I had to google around for. It would be nice to not have to figure this stuff out with your framework too and would encourage me to try it out more quickly.
I know that formik and redux-forms are popular, but react-final-form and your framework seem to have a lot of overlap. Both seem to be pretty well done.
[1] https://gist.github.com/lookfirst/558fb2161fd633c7eaab412b85...
Can't wait to use it on a real project soon. In fact, i might start converting some older things to it in spare time.
The website and docs are absolutely amazing, best i've seen in a long time. Thank you for doing this!
What ever happened to care in our profession? What happened to putting the users first? If you are adding 5kb of overhead to your website for this, it is criminal malpractice.
They are not equivalent at all, you can add as much bloat as you want on your backend as long as you scale it. The cost is put on you. Front-end bloat directly affects the user. We used to justify every library addition, now people just add a library for every trivial task to make it slightly "nicer" as if that matters at all.
class MyForm extends React.Component {
formState = new ReduxHookForm();
render() {
const { register, handleSubmit } = this.formState;
return (
<form onSubmit={handleSubmit(this.handleSubmit)}>
<input name="firstName" ref={register} />
<select name="gender" ref={register}>
<option value="male">male</option>
<option value="female">female</option>
</select>
<input type="submit" />
</form>
);
}
handleSubmit(data) {
console.log(data);
}
}