Update React Component State Based on External DOM Events

Andy Van Slaars
InstructorAndy Van Slaars
Share this video with your friends

Social Share Links

Send Tweet
Published 7 years ago
Updated 5 years ago

Most of the time, your components respond to events that occur within the component tree by defining their own handler or by accepting a handler defined by a parent component via props. Sometimes, this isn't enough. In this lesson, we'll rely on lifecycle hooks and good old fashioned DOM events to update state in a React component in response to an event that occurs outside of the component tree.

[00:00] My entry point is importing a menu component, and my state has a single key called currentChoice. Choosing an option from the menu will update the state with the choice that was selected from the menu, and I have the option to clear the selection. Let's look at the menu component to see how it works.

[00:21] Our menu component is made up of a DIV that has a link that calls this.toggleOptions when clicked. toggleOptions will set the isVisible property on the state, and then we're showing our sub items based on this.state.isVisible.

[00:33] Each of these submenu items has an onClick handler that calls this.handleClick, and it's partially applied with an argument so that we can pass the option for that particular link. If we scroll back up, we'll see toggle option simply prevents default, so that's our state. handleClick also prevents default, and it calls this.props.handleMenuChoice with the choice that's been partially applied to the function down here.

[01:01] It also sets the isVisible setting in the state to false. If we click through again, we'll see that toggling the state works, selecting an item will send that back up to the parent which updates the display based on the passed option. I can clear my selection.

[01:16] The problem comes in when I expand my menu and I click outside of it. This has no effect, in fact I have to click on the top level or select an item to get the menu to hide. Let's update this component so that the sub menu is hidden when there's a click outside of the component.

[01:33] I'm going to start by tabbing into the componentDidMount lifecycle event. In here I'm going to add an event handler through document.addEventListener, and I'm going to listen for a click event. On any click event I'm going to have a call method that will create a second called this.handleClickOutside. Now let's define that.

[02:07] I'll define handleClickOutside as a method that's going to accept an event. For now, I'm just going to console.log the event.target, and I'll save this and I'll open the console. I'm going to expand my menu, and we'll see that we're getting click events. We're going to get click events for any click that happens anywhere on the page.

[02:38] Before we implement handleClickOutside, I'm also going to implement the componentWillUnmount lifecycle event. Here we're going to call document.removeEventListener, and we want to remove that click event listener so that when this component is unmounted we don't leave this hanging around and create a memory leak. That will be this.handleClickOutside.

[03:10] With that done, let's implement our handleClickOutside method. I'm going to come down here and I'm going to grab the call to this.setState. It sets the isVisible property to false. I'm going to paste that right inside handleClickOutside.

[03:28] I'm going to save this, and now what's going to happen is nothing is going to work, because every click event that happens is going to be handled by this method and it's going to set the isVisible property of the state to false.

[03:42] What we need to do is check to make sure that where we've clicked is not inside of our component. In order to do that I'm going to use a reference. I'm going to come down here to my surrounding DIV, and I'm going to give this a REF property. REF is going to be a function that's going to take our element as an argument, and what we're going to do here is we're going to call this.node and we're going to set that to equal the element that's passed in as our reference.

[04:09] Now our component has a property on this called node, that refers to the DOM element for this DIV. With that reference in place, I'm going to come back up here and I'm going to use an if statement here, and I'm going to to say if not this.node, which is going to be the element for our wrapper DIV, .contains, and I'm going to reference event.target.

[04:35] Then I'm going to wrap my call to this.setState right in there. What this is going to do is it's going to make sure that the click that we're detecting only sets the state if the node that was clicked is not contained inside of our wrapper DIV for this component. Now I can save this, my menu's back to working, and I can make selections. If I expand it, and I click out here on the body, my menu will be hidden.

Scott
Scott
~ 7 years ago

What is the advantage to not using a constructor class method and instead defining state directly in the class?

Markdown supported.
Become a member to join the discussionEnroll Today