Forms are a basic building block of the web. Every web application uses form elements as a way to accept input from the user. There are a few things to keep in mind with how forms work on the web and in this lesson we’ll learn about those as well as various ways you can retrieve values from elements in the form as well as a few best practices you should consider when working with form elements on the web.
You can learn more about basic forms in the React documentation about Uncontrolled Components.
Kent C. Dodds: [0:00] We have this username form, and right now we're just returning todo, but we want to return a form. I'm going to make a form element here, and then we'll make a div to hold our label username, and then we'll have an input of type text for our username input, and then we'll add a button of type submit. This is our submit button, and we'll say submit. We'll save that, we'll get a refresh, and here's our form. Awesome.
[0:33] We could put an onClick handler on our submit button here, and that would work OK when we click on this button. Forms are actually automatically submitted when you hit enter in an input within the form. People are typically used to that user experience.
[0:48] What I'm going to do instead is add an onSubmit handler here, and if somebody clicks on the submit button, or if they hit enter in this input, then our onSubmit handler is going to be called.
[0:59] Let's make a function called handleSubmit. Then, we'll put that handleSubmit right in here and we'll console log submitted. Let's save that, get a refresh, say, Joe, hit submit and...Huh. That's interesting, it goes away.
[1:18] Actually, if you watch closely, you'll see that it does log but then we get a full page refresh. We can prove that by turning on preserve log. We say Joe again, hit submit, we get submitted and then navigated to that page.
[1:32] Now, the reason that this is happening is because when you submit a form in the browser, it automatically makes a post request to the current URL with the form data. We could see that if we looked at our network tab.
[1:54] The handleSubmit function as an event handler will accept an event as the argument. Then we can say event.preventDefault. With that, we will no longer get a full page refresh and we'll still get the submitted string logged to the console.
[2:09] Our next step is to get the value out of this input because we want to alert the value that the user entered. We need to get our username equals something and we'll say alert, "You entered username." We can get rid of that console log here. We can say Joe and you entered question mark.
[2:31] How are we going to get that username value? Well, one way we could do this, we could say document.querySelectorInput.value, save that. We say Joe, submit that and here we go, we get Joe. That's not going to work very well. It won't scale super well in the real world because the page could have multiple inputs.
[2:52] If we were to render multiples of these, then they could conflict with one another. Querying the entire document breaks the encapsulation of this component because it means that you can't use this component in the context of other components in an application. We don't want to query the document.
[3:09] Another thing we can do is we can access the form element from the event. We could say console.logEvent.target and that is going to be our form element. We say Joe, submit that and we'll get the form element right there.
[3:25] That's great, but let's take a look at some of the properties on that form element. Chrome is rendering out this really cool DOM tree similar to what we see in the elements tab, but I want to see the properties that are on the form elements. I'm going to use console.dir, which will log out the properties of that elements.
[3:41] We'll say Joe, submit and here we go, we have our form. Here, there are a bunch of properties on here. We can see there's a zero and a one for input and button, so we could actually say username is event.target. We want zero.value, we save this and we get Joe and we get our, "You entered Joe," alert showing up.
[4:04] That works out nicely, but I'm going to get the username from another one of these properties. Let's see, we've got elements right here and elements has a zero and one. We could say event.target.elements.zero.value and save that. That will work as well.
[4:24] I'm not super jazzed about relying implicitly on the order in which these form elements appear because if I were to had another input right here, then I'm going to be toast, "You entered undefined. I don't want to rely on that."
[4:39] There's another thing that we can do here and that is by properly associating our label to the input by having an HTML for username input. Then, having an ID of username input right here. Now the label and the input are properly associated. Meaning that I can click on the label and it will focus on the input, which is good for accessibility.
[5:03] When I say Joe in here, hit submit, then we look at our form in our elements property. In addition to the zero and one indexes for each of these elements, we also get this username input value here because the ID of this element inside of our form. You actually get the same thing using the name attribute as well.
[5:29] We look at our form elements, and we see that username input still exists right there.
[5:34] Any elements that have a name or an ID will be attached by that name and ID to this element's property of the form node. You can reference those by those names and IDs. That's what we're going to prefer here. We'll say username = event.target -- that's our form -- .elements.usernameInput.value to get the value out of that username input.
[5:58] With that, we can say Joe and we get everything working exactly as we want without the implicitness of querying the entire document or relying on the order of the elements.
[6:09] Another thing that we could do is make a ref for our username input. We'll say usernameInputRef = React.useRef, and then we could say ref = usernameInputRef, and instead of this, we could say username = usernameInputRef.current.value. Our usernameInputRef.current will be the DOM node for the input, and we'll get the value from that. This will work as well.
[6:43] There are a couple of ways to do this, and I'll go ahead and add username = document.querySelector input.value. The reason that I removed that is I really want you to please not use this. It breaks encapsulation of our components. That's one of the things that we really love about React, is that we can encapsulate logic within our components.
[7:05] I don't recommend this method or this method, because those implicitly rely on the order in which the elements are rendered and could easily break if you change that order. Either one of these will work just fine.
[7:16] Personally, I think that if you don't need a ref for the input, that it's just easier to retrieve the input from the event target elements. It's recommended that you use HTML for an ID to associate labels to inputs anyway. Given that you already have to do that, this, I think, is the simplest way to get the value from the inputs in your form.
[7:36] With that in mind, let's go ahead and get rid of all of this. We get rid of that, clean this up, and remove the ref. If I were writing this form today, this is what it would look like.
[7:46] In review, what we had to do here was we created a form that has the single input and label associated to that input, and a button with the type of submit. It's really important that any button you put inside of a form has a type, because implicitly, it will have a type of submit. That's not entirely clear, especially if you say cancel for the contents with no specified type that gives this button any submit type, which is really confusing.
[8:12] If you do want to have a cancel button or a reset button, then you want to specify the type is button, which I know is a little redundant, but if you don't specify a type, then the type will implicitly be submit, which would really confuse your users when they try to click cancel, so we specify that type is submit. We put in submit for the value here.
[8:34] Then instead of putting an onClick handler on the button, we put an onSubmit handler on the form. That way, any other way that the user tries to submit the form will call our submit handler.
[8:44] Then to avoid a full page refresh, we use event.preventDefault, and then we retrieve the user's input using event target to get the form, and then elements to get the elements of the form and usernameInput to retrieve the input by its ID. Then we get the value from that input to get our username, and then we alert that to our users, or you could submit this to a backend server.
Thank you for the explanation about the HTML form and the advice of how to use it properly. I just have a question regarding to the ID of the input element. Will it cause the confliction if there is another similar form on the page? Since the form can be extracted to be on a component and if it is reused, then there are duplication of element's IDs. If so, what is better way to resolve it? Thanks.
Yes, you will want to ensure the ID is unique across the page. You can definitely generate it though. I think the best implementation of something like this is
useId from Reach UI: https://reacttraining.com/reach-ui/auto-id/
Thank you very much for your prompt response and solution.