left-icon

React.js Succinctly®
by Samer Buna

Previous
Chapter

of
A
A
A

CHAPTER 7

Working with User Input

Working with User Input


We modeled our data state and declaratively defined our UI as a function of our state. It’s now time to start accounting for UI events and capturing input from the user.

The nature of user input can be described as a reverse data flow when compared to how React’s components represent data. In fact, with active user input on a page, the data starts depending on the UI.

Suppose, for example, we have a UI that displays a list of comments. We start with an array of comments in the data layer, make the UI represent that, and then add a form on the page to add a new comment. Our array of data now depends on the user interactions with this form.

If we handle the input events, capture the data that users enter, and then manipulate our original data source to account for the new input, React will refresh the views that are using that data.

I’ll first explain the simple way tofor working with user input: Rrefs Aattributes. Then I’ll then explain how to work with user input using the recommended way: Ccontrolled Ccomponents.

Before we go into that, however, we need to understand the synthetic events system in React.

React’s synthetic events

We can define native browser events on DOM elements. For example, to console.log the content of a div when it's clicked, we can do the following:

Code Listing 50: HTML event

<div onclick="console.log(this.textContent);">

  You might not need React...

</div>

To do that exact same thing in React, we use an onClick synthetic event.:

Code Listing 51: React event

<div onClick={se => console.log(se.target.textContent)} >

  You might not need React...

</div>

Differences:The differences between these events are:

  • Casinge is important;. React uses camelCase for the event attributes (onClick, onKeyPress, …).
  • We don’t use strings with React events; we use JavaScript directly.
  • We use a function for the value. The function gets defined when we define the component, and it will be invoked when we click on the element.

An onClick used within a React component works with a synthetic event. That’s why I named the variable passed to the function “se”. React’s synthetic events are wrappers around the browser’s native events. We can still access the native event object itself using the nativeEvent attribute on any synthetic event. We can see the native event if we console.log(se.nativeEvent) in the previous example.

Browsers historically had significant differences in the way they handled and invoked their native events. This is getting a lot better with the W3C standards now, but browsers’ quirks are not to be trusted. With React synthetic events, we don’t have to worry about that. React will make sure events work identically across all browsers.

One notable example of an event that React made standard between multiple inputs and in all browsers is the onChange event. The native onChange event works differently among different input types, and it usually does not mean “change,” but rather that “the user is done changing.” With React events, the onChange event means that on any change, anywhere, anytime, and for any input:

  • If we’re typing in an input or textarea element, React will fire the onChange event on every key stroke (much like keyboard events, but also with support for paste and automated entries).
  • If we check select or uncheck clear a check box, React will fire the onChange event.
  • When we select an option from a drop-down select element, React will fire the onChange event.

Here are examples of some of the popular events that we can use with React:

  • Keyboard Eevents: onKeyDown, onKeyPress, onKeyUp
  • Focus Eevents: onFocus, onBlur
  • Form Eevents: onChange, onInput, onSubmit
  • Touch Eevents: onTouchStart, onTouchEnd, onTouchMove, onTouchCancel
  • Mouse Eevents: onClick, onDrag, onMouseOver, onMouseOut, etc. …

There are also Cclipboard Eevents, UI Eevents, Wwheel Eevents, Ccomposition Eevents, Mmedia Eevents, and Iimage Eevents.

We can even work with events that are not supported in React by using addEventListener (usually in componentDidMount) and removeEventListener (in componentWillUnmount).

Working with DOM nodes in the browser

React’s powerful performance feature, the vVirtual DOM, frees us from ever needing to touch the DOM. When adding React’s synthetic events to the mix, React (in a way) has created a “fake browser” that respects all the standards, is way faster, and is a lot easier to work with.

This, however, does not mean that we can’t use the original browser’s DOM (and events) with React if we need to. Sometimes we need to do special things with the DOM, such as integrate ing our components with third-party libraries.

If we need to access a native DOM node in any React component, we can use React’s special attribute,: ref.

We can pass that attribute either a string or a function. You’ll find a lot of examples that are usinge a string value (which acts like a unique identifier), but don’t do that; instead, use functions.

To work through an example, let’s say we have an email input field where the user is supposed to type an email, and we have a Save button. Before we save, we want to make sure that the input is a valid email. If we use the <input type="email" /> element, we can use the native API method element.checkValidity() to figure out ifwhether or not the user entered a valid email or not. To invoke that API method, we need access to the native DOM element.

Here’s the full example implemented with React’s ref attribute:

Code Listing 52: Ref Attributes

const EmailForm = React.createClass({

  handleClick() {

    if (this.inputRef.checkValidity()) {

      console.log(`Email Ok. Saving Email as ${this.inputRef.value}`);

    }

  },

  render() {

    return (

      <div>

        <input type="email" ref={inputRef => this.inputRef = inputRef} />

        <button onClick={this.handleClick}>Save</button>

      </div>

    );

  }

})

ReactDOM.render(<EmailForm />, document.getElementById("react"));

When we define a ref attribute on the input element and use a function for its value, React will execute that function when the input element gets mounted with the EmailForm component. React will also pass a reference to the DOM input element (inputRef in the example) as an argument to that ref function. Inside the ref function, we can access the EmailForm component instance via the this keyword, so we can store the input reference as an instance variable.

Once we have the reference to the native DOM element (this.inputRef), we can access all of its API normally. In handleClick, we are calling the native checkValidity() function on the DOM reference.

We can use the same trick to read the value of any input at any point. In handleClick, the console.log line actually reports the email input text value (using the native API property “value”) when the input is valid.

Changing a React component’s state with native API calls

We can use React's ref attributes to read input from the user by assigning component variables to input fields like we did in the previous example. However, every time the user types something into an input field, they’re practically changing the “state” of that field. Since we are representing elements with stateful components in React, not reflecting the input state change back to the enclosing component’s state means that our component is no longer an exact representation of its current state that is defined in React.

Here’s an example to make that point easy to understand:

Code Listing 53: Input State

const EmailForm = React.createClass({

  getInitialState() {

    return {

      currentEmail: this.props.currentEmail

    };

  },

  render() {

    return (

      <div>

        <input type="email" value={this.state.currentEmail} />

        <button>Save</button>

      </div>

    );

  }

})

ReactDOM.render(

  <EmailForm currentEmail="[email protected]" />,

  document.getElementById("react")

);

We rendered the same EmailForm to save an email field, but this time we’re displaying an initial value of the email field, and users can change it if they want (an “edit” feature).

Since the email is something that can be changed in this component, we put it on the state of the component. React rendered the input email and used the component state to display the default value. If we change the currentEmail state in memory, React will update the value displayed in the input box.

However, if the user is allowed to change the value of that input field directly using the browser API (by typing into the text box), then the displayed DOM in the browser will be different than the current copy of the virtual DOM that we have in memory (because that one is reflecting the currentEmail state, which has not changed). If something else changes in the state of the EmailForm component, React will re-render the EmailForm component, and the value the user typed in the email will be lost. This is why React would not allow the user to type in this example’s email input.

React components should always represent their state in the DOM, and the input field is part of that state. If we can make the input field always represent the React’s component's state, even when the user types in it, then we can just read React’s state whenever we need to read the new input from the user.

A component where we control the input to always reflect the state is called a controlled component.

Controlled components

By using controlled components, we don’t have to reach to an input field's native DOM element to read its value, since whatever the user types in there will be reflected on the component state itself.

To achieve this level of control, we use an onChange event handler. Every time a change event is fired, we update the component state associated with the input.

Here’s the previous example updated to be a controlled component:

Code Listing 54: Controlled Component

const EmailForm = React.createClass({

  getInitialState() {

    return { currentEmail: this.props.currentEmail };

  },

  setCurrentEmailState(se) {

    this.setState({ currentEmail: se.target.value });

  },

  handleClick() {

    console.log(`Saving New Email value: ${this.state.currentEmail}`);

  },

  render() {

    return (

      <div>

        <input type="email" value={this.state.currentEmail}

               onChange={this.setCurrentEmailState} />

        <button onClick={this.handleClick}>Save</button>

      </div>

    );

  }

})

ReactDOM.render(

  <EmailForm currentEmail="[email protected]" />,

  document.getElementById("react")

);

By using the setCurrentEmailState onChange handler, we’re updating the state of the EmailForm component every time the user types into the email field. This way we make sure that the new DOM in the browser and React’s in-memory virtual DOM are both in sync, reflecting the current state of the EmailForm component.

Note how in the console.log line, we can read any new input value the user entered using this.state.currentEmail.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.