This post is part of the series 30 Days of React.

In this series, we're starting from the very basics and walk through everything you need to know to get started with React. If you've ever wanted to learn React, this is the place to start!

Interactivity

Edit this page on Github

Today we'll walk through how to add interactivity to our applications to make them engaging and dynamic.

Through this point, we've built our few handful of components without adding much user interaction. Today, we're going to change that.

User interaction

The browser is an event-driven application. Everything that a user does in the browser fires an event, from clicking buttons to even just moving the mouse. In plain JavaScript, we can listen for these events and attach a JavaScript function to interact with them.

For instance, we can attach a function to the mousemove browser event with the JS:

export const go = () => {
  const ele = document.getElementById('mousemove');
  ele.innerHTML = 'Move your mouse to see the demo';
  ele.addEventListener('mousemove', function(evt) {
    const { screenX, screenY } = evt;
    ele.innerHTML = '<div>Mouse is at: X: ' +
          screenX + ', Y: ' + screenY +
                    '</div>';
  })
}

This results in the following functionality:

Move your mouse over this text

In React, however we don't have to interact with the browser's event loop in raw JavaScript as React provides a way for us to handle events using props.

For instance, to listen for the mousemove event from the (rather unimpressive) demo above in React, we'll set the prop onMouseMove (notice the camelcasing of the event name).

<div onMouseMove={(evt) => console.log(evt)}>
  Move the mouse
</div>

React provides a lot of props we can set to listen for different browser events, such as click, touch, drag, scroll, selection events, and many more (see the events documentation for a list of all of them).

To see some of these in action, the following is a small demo of some of the props we can pass on our elements. Each text element in the list set the prop it lists. Try playing around with the list and seeing how the events are called and handled within the element.

We'll be using the onClick prop quite a bit all throughout our apps quite a bit, so it's a good idea to be familiar with it. In our activity list header, we have a search icon that we haven't hooked up yet to show a search box.

The interaction we want is to show a search <input /> when our users click on the search icon. Recall that our Header component is implemented like this:

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }

  // toggle visibility when run on the state
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }

  render() {
    // Classes to add to the <input /> element
    let searchInputClasses = ["searchInput"];

    // Update the class array if the state is visible
    if (this.state.searchVisible) {
      searchInputClasses.push("active");
    }

    return (
      <div className="header">
        <div className="menuIcon">
          <div className="dashTop"></div>
          <div className="dashBottom"></div>
          <div className="circle"></div>
        </div>

        <span className="title">
          {this.props.title}
        </span>

        <input
          type="text"
          className={searchInputClasses.join(' ')}
          placeholder="Search ..." />

        {/* Adding an onClick handler to call the showSearch button */}
        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

When the user clicks on the <div className="fa fa-search searchIcon"></div> element, we'll want to run a function to update the state of the component so the searchInputClasses object gets updated. Using the onClick handler, this is pretty simple.

Let's make this component stateful (it needs to track if the search field should be showing or not). We can convert our component to be stateful using the constructor() function:

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }
  // ...
}

What is a constructor function?

In JavaScript, the constructor function is a function that runs when an object is created. It returns a reference to the Object function that created the instance's prototype.

In plain English, a constructor function is the function that runs when the JavaScript runtime creates a new object. We'll use the constructor method to set up instance variables on the object as it runs right when the object is created.

When using the ES6 class syntax to create an object, we have to call the super() method before any other method. Calling the super() function calls the parent class's constructor() function. We'll call it with the same arguments as the constructor() function of our class is called with.

When the user clicks on the button, we'll want to update the state to say that the searchVisible flag gets updated. Since we'll want the user to be able to close/hide the <input /> field after clicking on the search icon for a second time, we'll toggle the state rather than just set it to true.

Let's create this method to bind our click event:

class Header extends React.Component {
  // ...
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }
  // ...
}

Finally, we can attach a click handler (using the onClick prop) on the icon element to call our new showSearch() method. The entire updated source for our Header component looks like this:

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }

  // toggle visibility when run on the state
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }

  render() {
    // Classes to add to the <input /> element
    let searchInputClasses = ["searchInput"];

    // Update the class array if the state is visible
    if (this.state.searchVisible) {
      searchInputClasses.push("active");
    }

    return (
      <div className="header">
        <div className="fa fa-more"></div>

        <span className="title">
          {this.props.title}
        </span>

        <input
          type="text"
          className={searchInputClasses.join(' ')}
          placeholder="Search ..." />

        {/* Adding an onClick handler to call the showSearch button */}
        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

Try clicking on the search icon and watch the input field appear and disappear (the animation effect is handled by CSS animations).

Input events

Whenever we build a form in React, we'll use the input events offered by React. Most notably, we'll use the onSubmit() and onChange() props most often.

Let's update our search box demo to capture the text inside the search field when it updates. Whenever an <input /> field has the onChange() prop set, it will call the function every time the field changes. When we click on it and start typing, the function will be called.

Using this prop, we can capture the value of the field in our state.

Rather than updating our <Header /> component, let's create a new child component to contain a <form /> element. By moving the form-handling responsibilities to it's own form, we can simplify the <Header /> code and we can call up to the parent of the header when our user submits the form (this is a usual React pattern).

Let's create a new component we'll call SearchForm. This new component is a stateful component as we'll need to hold on to the value of the search input (track it as it changes):

class SearchForm extends React.Component {
  // ...
  constructor(props) {
    super(props);

    this.state = {
      searchText: ''
    }
  }
  // ...
}

Now, we already have the HTML for the form written in the <Header /> component, so let's grab that from our Header component and return it from our SearchForm.render() function:

class SearchForm extends React.Component {
  // ...
  render() {
    const { searchVisible } = this.state;
    let searchClasses = ['searchInput']
    if (searchVisible) {
      searchClasses.push('active')
    }

    return (
      <form className='header'>
        <input
          type="search"
          className={searchClasses.join(' ')}
          onChange={this.updateSearchInput.bind(this)}
          placeholder="Search ..." />

        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </form>
    );
  }
}

Notice that we lost the styles on our <input /> field. Since we no longer have the searchVisible state in our new component, we can't use it to style the <input /> any longer. However, we can pass a prop from our Header component that tells the SearchForm to render the input as visible.

Let's define the searchVisible prop (using React.PropTypes, of course) and update the render function to use the new prop value to show (or hide) the search <input />. We'll also set a default value for the visibility of the field to be false (since our Header shows/hides it nicely):

class SearchForm extends React.Component {
  static propTypes = {
    onSubmit: React.PropTypes.func.isRequired,
    searchVisible: React.PropTypes.bool
  }
  // ...
}

Now we have our styles back on the <input /> element, let's add the functionality for when the user types in the search box, we'll want to capture the value of the search field. We can achieve this workflow by attaching the onChange prop to the <input /> element and passing it a function to call every time the <input /> element is changed.

class SearchForm extends React.Component {
  // ...
  updateSearchInput(e) {
    const val = e.target.value;
    this.setState({
      searchText: val
    });
  }
  // ...
  render() {
    const { searchVisible } = this.state;
    let searchClasses = ['searchInput']
    if (searchVisible) {
      searchClasses.push('active')
    }

    return (
      <form className='header'>
        <input
          type="search"
          className={searchClasses.join(' ')}
          onChange={this.updateSearchInput.bind(this)}
          placeholder="Search ..." />

        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </form>
    );
  }
}

When we type in the field, the updateSearchInput() function will be called. We'll keep track of the value of the form by updating the state. In the updateSearchInput() function, we can call directly to this.setState() to update the state of the component.

The value is held on the event object's target as event.target.value.

class SearchForm extends React.Component {
  // ...
  updateSearchInput(e) {
    const val = e.target.value;
    this.setState({
      searchText: val
    });
  }
  // ...
}

Controlled vs. uncontrolled

We're creating what's known as an uncontrolled component as we're not setting the value of the <input /> element. We can't provide any validation or post-processing on the input text value as it stands right now.

If we want to validate the field or manipulate the value of the <input /> component, we'll have to create what is called a controlled component, which really just means that we pass it a value using the value prop. A controlled component version's render() function would look like:

class SearchForm extends React.Component {
  render() {
    return (
      <input
        type="search"
        value={this.state.searchText}
        className={searchInputClasses}
        onChange={this.updateSearchInput.bind(this)}
        placeholder="Search ..." />
    );
  }
}

As of now, we have no way to actually submit the form, so our user's can't really search. Let's change this. We'll want to wrap the <input /> component in a <form /> DOM element so our users can press the enter key to submit the form. We can capture the form submission by using the onSubmit prop on the <form /> element.

Let's update the render() function to reflect this change.

class SearchForm extends React.Component {
  // ...
  submitForm(e) {
    e.preventDefault();

    const {searchText} = this.state;
    this.props.onSubmit(searchText);
  }
  // ...
  render() {
    const { searchVisible } = this.props;
    let searchClasses = ['searchInput']
    if (searchVisible) {
      searchClasses.push('active')
    }

    return (
      <form onSubmit={this.submitForm.bind(this)}>
        <input
          type="search"
          className={searchClasses.join(' ')}
          onChange={this.updateSearchInput.bind(this)}
          placeholder="Search ..." />
      </form>
    );
  }
}

We immediately call event.preventDefault() on the submitForm() function. This stops the browser from bubbling the event up which would causes the default behavior of the entire page to reload (the default function when a browser submits a form).

Now when we type into the <input /> field and press enter, the submitForm() function gets called with the event object.

So... great, we can submit the form and stuff, but when do we actually do the searching? For demonstration purposes right now, we'll pass the search text up the parent-child component chain so the Header can decide what to search.

The SearchForm component certainly doesn't know what it's searching, so we'll have to pass the responsibility up the chain. We'll use this callback strategy quite a bit.

In order to pass the search functionality up the chain, our SearchForm will need to accept a prop function to call when the form is submitted. Let's define a prop we'll call onSubmit that we can pass to our SearchForm component. Being good developers, we'll also add a default prop value and a propType for this onSubmit function. Since we'll want to make sure the onSubmit() is defined, we'll set the onSubmit prop to be a required prop:

class SearchForm extends React.Component {
  static propTypes = {
    onSubmit: React.PropTypes.func.isRequired,
    searchVisible: React.PropTypes.bool
  }
  // ...
  static defaultProps = {
    onSubmit: () => {},
    searchVisible: false
  }
  // ...
}

When the form is submitted, we can call this function directly from the props. Since we're keeping track of the search text in our state, we can call the function with the searchText value in the state so the onSubmit() function only gets the value and doesn't need to deal with an event.

class SearchForm extends React.Component {
  // ...
  submitForm(event) {
    // prevent the form from reloading the entire page
    event.preventDefault();
    // call the callback with the search value
    this.props.onSubmit(this.state.searchText);
  }
}

Now, when the user presses enter we can call this onSubmit() function passed in the props by our Header component.

We can use this SearchForm component in our Header component and pass it the two props we've defined (searchVisible and onSubmit):

import React from 'react';
import SearchForm from './SearchFormWithSubmit'

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }

  // toggle visibility when run on the state
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }

  render() {
    // Classes to add to the <input /> element
    let searchInputClasses = ["searchInput"];

    // Update the class array if the state is visible
    if (this.state.searchVisible) {
      searchInputClasses.push("active");
    }

    return (
      <div className="header">
        <div className="menuIcon">
          <div className="dashTop"></div>
          <div className="dashBottom"></div>
          <div className="circle"></div>
        </div>

        <span className="title">
          {this.props.title}
        </span>

        <SearchForm
          searchVisible={this.state.searchVisible}
          onSubmit={this.props.onSubmit} />

        {/* Adding an onClick handler to call the showSearch button */}
        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

export default Header

Now we have a search form component we can use and reuse across our app. Of course, we're not actually searching anything yet. Let's fix that and implement search.

To implement search in our component, we'll want to pass up the search responsibility one more level from our Header component to a container component we'll call Panel.

First things first, let's implement the same pattern of passing a callback to a parent component from within a child component from the Panel container to the Header component.

On the Header component, let's update the propTypes for a prop we'll define as a prop called onSearch:

class Header extends React.Component {
  // ...
}
Header.propTypes = {
  onSearch: React.PropTypes.func
}

Inside the Header component's 'submitForm()' function, call this onSearch() prop we will pass into it:

class Header extends React.Component {
  // ...
  submitForm(val) {
    this.props.onSearch(val);
  }
  // ...
}
Header.propTypes = {
  onSearch: React.PropTypes.func
}

Notice that our virtual tree looks like this:

<Panel>
  <Header>
    <SearchForm></SearchForm>
  </Header>
</Panel>

When the <SearchForm /> is updated, it will pass along it's awareness of the search input's change to it's parent, the <Header />, when it will pass along upwards to the <Panel /> component. This method is very common in React apps and provides a good set of functional isolation for our components.

Back in our Panel component we built on day 7, we'll pass a function to the Header as the onSearch() prop on the Header. What we're saying here is that when the search form has been submitted, we want the search form to call back to the header component which will then call to the Panel component to handle the search.

Since the Header component doesn't control the content listing, the Panel component does, we have to pass the responsibility one more level up, as we're defining here.

In any case, our Panel component is essentially a copy of our Content component we previously worked on:

class Panel extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false, // <~ set loading to false
      activities: data,
      filtered: data,
    }
  }

  componentDidMount() {this.updateData();}
  componentWillReceiveProps(nextProps) {
    // Check to see if the requestRefresh prop has changed
    if (nextProps.requestRefresh !== this.props.requestRefresh) {
      this.setState({loading: true}, this.updateData);
    }
  }

  handleSearch = txt => {
    if (txt === '') {
      this.setState({
        filtered: this.state.activities
      })
    } else {
      const { activities } = this.state
      const filtered = activities.filter(a => a.actor && a.actor.login.match(txt))
      this.setState({
        filtered
      })
    }
  }

  // Call out to github and refresh directory
  updateData() {
    this.setState({
      loading: false,
      activities: data
    }, this.props.onComponentRefresh);
  }

  render() {
    const {loading, filtered} = this.state;
    
    return (
      <div>
        <Header
          onSubmit={this.handleSearch}
          title="Github activity" />
        <div className="content">
          <div className="line"></div>
          {/* Show loading message if loading */}
          {loading && <div>Loading</div>}
          {/* Timeline item */}
          {filtered.map((activity) => (
            <ActivityItem
              key={activity.id}
              activity={activity} />
          ))}
          
        </div>
      </div>
    )
  }
}

Let's update our state to include a searchFilter string, which will just be the searching value:

class Panel extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      searchFilter: '',
      activities: []
    }
  }
}

In order to actually handle the searching, we'll need to pass a onSearch() function to our Header component. Let's define an onSearch() function in our Panel component and pass it off to the Header props in the render() function:

class Panel extends React.Component {
  // ...
  // after the content has refreshed, we want to 
  // reset the loading variable
  onComponentRefresh() {this.setState({loading: false});}

  handleSearch(val) {
    // handle search here
  }

  render() {
    const {loading} = this.state;
    
    return (
      <div>
        <Header
          onSearch={this.handleSearch.bind(this)} 
          title="Github activity" />
        <Content 
          requestRefresh={loading}
          onComponentRefresh={this.onComponentRefresh.bind(this)}
          fetchData={this.updateData.bind(this)} />
      </div>
    )
  }
}

All we did here was add a handleSearch() function and pass it to the header. Now when the user types in the search box, the handleSearch() function on our Panel component will be called.

To actually implement search, we'll need to keep track of this string and update our updateData() function to take into account search filtering. First, let's set the searchFilter on the state. We can also force the Content to reload the data by setting loading to true, so we can do this in one step:

class Panel extends React.Component {
  // ...
  handleSearch(val) {
    this.setState({
      searchFilter: val,
      loading: true
    });
  }
  // ...
}

Lastly, let's update our updateData() function to take search into account.

class SearchableContent extends React.Component {
  // ...
      this.setState({loading: true}, this.updateData);
  // ...
}

Although this might look complex, it's actually nearly identical to our existing updateData() function with the exception of the fact that we updated our fetch() result to call the filter() method on the json collection.

All the collection.filter() function does is run the function passed in for every element and it filters out the values that return falsy values, keeping the ones that return truthy ones. Our search function simply looks for a match on the Github activity's actor.login (the Github user) to see if it regexp-matches the searchFilter value.

With the updateData() function updated, our search is complete.

Try searching for auser.

Now we have a 3-layer app component that handles search from a nested child component. We jumped from beginner to intermediate with this post. Pat yourself on the back. This was some hefty material. Make sure you understand this because we'll use these concepts we covered today quite often.

In the next section, we'll jump out and look at building pure components.


Ari Lerner

Hi, I'm Ari. I'm an author of Fullstack React and ng-book and I've been teaching Web Development for a long time. I like to speak at conferences and eat spicy food. I technically got paid while I traveled the country as a professional comedian, but have come to terms with the fact that I am not funny.

Connect with Ari on Twitter at @auser.