In this lesson, we'll talk about controlling the value for
select elements. We'll see how to manage the state ourselves while still allowing the user to update the values themselves.
Instructor: [00:00] Here, we have my fancy form, and it renders an input, a text, and a select box that allows us to select multiple values. On each one of these, we have an onChange handler that's hooked up to its own onChange handler here.
[00:15] What we want to do is we want to synchronize the state of all of these things. For example, if I were to type apple, cherry, then the multi-line values would automatically add apple and cherry, and then the multi-select would automatically select apple and cherry.
[00:34] To be able to do this, we need to control the state of the value of each of these inputs, and then update that state ourselves.
[00:42] Let's start out by controlling the input and the text area values. To do that, we need to keep that state somewhere ourselves. I'm going to add a state property here. That's going to have multi-line as an empty string, and comma-separated as an empty string also.
[01:03] We need to explicitly set the value on these input fields. On this input, I'm going to say value equals comma-separated, and we'll pull that from state, so comma-separated equals this.state. We're also going to set the value on our text area to be multi-line. We'll pull that from our state also.
[01:27] Let's go ahead and try things out. I am typing, and nothing's happening. What happens is, the moment that you put a value prop on an input or a text area, it now becomes impossible for the user to update that value themselves.
[01:44] You're telling React that I don't really care what the user is doing. I am in control of this value. You have to use this onChange prop to handle any time the user is making a change, and then you're in control of taking that change into account, to update the value of the input.
[02:02] We'll go ahead and go to this handle comma-separator change function here. If we console.log the event.target.value, and then look at our developer console, then we can type a bunch of letters, and we're seeing that those letters are being updated, but the value of the input is never being updated, because we're in control of that.
[02:23] We're going to say this.setState, and then for our comma-separated value, that will simply be event.target.value. Let's go and save that, and see what happens here. Now, I can type.
[02:37] What's happening is, every single time this change event happens, we update the comma-separated state, and when we call setState, that's going to force a re-render, and now, we are updating this comma-separated state with whatever value the user typed in, and so we're passing that back into the input value, so React is updating that for us.
[02:57] Now, we want to update the multi-line state based on the comma-separated state. Let's go ahead, and I'm going to actually extract the value from event.target, then we can simply do value here.
[03:11] Then we'll set our multi-line state to be value.split on the comma, then we'll map these values to value.trim, to trim any whitespace that we have. Then we'll filter any that are empty strings, and then we'll join them all back together with a new line.
[03:31] We'll hide our developer tools there, and with that, we can say cherry, and grape, and pear, and peach. Cool. Now, let's do the opposite. If I type grape, looks like I'm typing in here, and nothing's happening. That's because we're in control of the text area, and we're not doing anything when the text area change event is happening. Let's go ahead and do that here.
[03:53] We'll say const value is event.target. Then we'll say this.setState, and comma-separated is going to be value.split on new lines. We'll map each value to trim, then filter the empty strings, and then join those again with a comma. For our multi-line state, we will simply put the value that is being typed. Cool.
[04:22] Now, I can say orange, and pear, and grape, and cherry. If I go back in here and I just remove pear, that will remove from the multi-line values. Let's go ahead and set the value on the select.
[04:36] OnSelect element, it's a little bit unique, in that we set the value here, and the value that you set on a multiple select will be an array. If it's not multiple, then you can simply put the string value of the option based on the value. For us, we are doing multiple, so we will do an array here. Let's store that in state.
[04:57] We'll do multi-select, and then in our de-structuring of the state here, we'll paste that there. Then we will initialize that right here with an empty array. Then when we type in the comma-separated input, we can say multi-select. We're basically going to be doing the same thing. We need to turn this into an array.
[05:19] We're going to pull this out and say all-vals is equal to that. Then we can change this to all-vals, and then here, we can say all-vals, but we don't want to have any value that needs to be available in the options here.
[05:34] We're going to say filter each value to be one that is contained in our available options. We'll say my-fancy-form.available-options.includes that value. If I save that, we can type in cherry, and then orange, peach, and apple. Then I can go ahead and remove this.
[05:56] That doesn't work, because when the multi-line values change, we're not updating the state of the multi-select. Let's go ahead and do that.
[06:03] We're going to do a very similar thing here. I'm going to call this all-vals, and then in here, we're going to say multi-select is all-vals. It's pretty much exactly this thing, actually. Let's just copy that in. Now, we can say grape, and cherry, and pear. Awesome.
[06:22] One last thing. I cannot change the selection by interacting with the multi-select, and that's because we're not doing anything with the change event on the multi-select. This one is going to be a little bit unique.
[06:32] I'm going to go ahead and console.log target at event.target. When I pop open my developer tools here, I'm going to click on this, and then we see all of our options available right here. There's also an options array available here, that shows us all the options. Then there's this really handy selected options array. That's the one that we're going to be looking at.
[06:53] Let's go ahead, and we will console.log event.target.selected-options. When I click on this, we're going to see it actually has that one option in there, but because we're not updating any state, that value of an empty array is going to be re-rendered and continue through this multi-select. That selection doesn't take hold.
[07:15] We'll say this.setState multi-select. We'll just call this all-vals. To get all-vals, we're going to say const all-vals=event.target.selected-options.map option option.value. If I go ahead and save this and then choose one, I'm going to get an error. That's because .map is not a function on an HTML node list.
[07:41] We can turn it into an array with array.from, and then we can call map on it. I can select any of these that I want. We'll say for multi-line, that's going to be all-vals.join, with a new line. For comma-separated, that will be all-vals.join with a comma. Now, I select this, and select that, and we get all those options added.
[08:06] In review, to control the input values, we have to specify a value property on the input, and on the text area, we specify a value property also, even though in normal HTML, the text area is contents, the children of the text area would be the value. In React, we use the value prop.
[08:24] OnSelect, for the value, if it's multiple select, then you can use an array. If it's not, then you simply use the string for the option that's selected.
[08:34] Then, if you want to respond to when the user is trying to update that value, you're going to need an onChange event handler, then you use the event that is passed to your onChange event handler to know what the value should be updated to, based off of what the user is selecting.
A good homework assignment may be to ensure custom values (e.g. banana, strawberry) remain in the comma-separated and multiline inputs after command-clicking a multi-select option.
availableOptions are declared
No reason in particular. I like to do that to indicate that
availableOptions are relevant only to the component that's using them. But it'd work just as well to declare them as a regular variable and use them that way.
I was reading the beginner docs from the React website and found this: https://reactjs.org/docs/lifting-state-up.html
Woudn't that be a better approach for this particular scenario?
I'm not sure I follow Billy. The reason you lift state up is when you want to share state between two components that are in different areas of the tree. So you put the state in the least-common-parent of those components. That's the process called "lifting state up."
In this example, I was showing how to control the state of form fields.
Was just rerunning the code from this example. It works almost as expected, but on my machine the select of multiple values in she multi select fails.
Forget my question above. Using the 'shift' or the 'command' key changes situation quite a bit ;-) Sorry, my bad.
Great course. One suggestion is, how about giving the initial code on codesandbox so that we can follow you by editing that code ???
That's a great idea. I'll let the Egghead folks know :)
Hi Kent! thanks for an awesome condensed course, and... pls help! I ran into a problem with multiSelect (it's also not working properly in your code example on Codebox). For some reason <code>Array.from(event.target.selectedOptions)</code> creates an array with only the LAST selected option, not ALL of them. However, if I do <code>console.log(event.target.selectedOptions)</code> I see HTMLCollction of a correct number of items (say, if I selected 3 options, I see 3 of them in HTML Collection). What do you think could be the problem? Thanks a lot!
Sorry Modesta, but it's working fine for me (just as demonstrated in the video).
Yup, I totally forgot to hold CMD key pressed to multiselect... no emoji to justify that, just shame :D
Why bother filtering the
multiSelect state value on