Panning For Gold: How to Implement a Simple React Filter

How to Implement a Simple React Filter

I want to share my initial experience with React and demonstrate how to implement a simple search feature. If you have a lot of JavaScript and React experience, move along. This is not the blog post you are looking for. But, if you have been hunkered down in a backend coding program for the past few months and are finally starting to think about the front end, this blog is for you.

React: Initial Thoughts

React is a front end JavaScript framework written by the good folks at Facebook. I've been working in React for a few weeks now and I am beginning to really appreciate it. One piece that I like a lot is the idea that React Components have state, which makes the whole process of building a front end feel more object oriented. Imagine a <div> in a blog site that holds the contents of the article. With React, in addition to actually having the text of an article the div can hold state - or things like whether or not the logged in user has liked the article, how many comments have been posted in response, what categories the post belongs to, etc. 

If like me, you are coming from a heavy Rails background and just getting started in React, I highly recommend this Creact tutorial published by Lovisa Svallingson.

ReactBox

For the purpose of showing how to implement a search feature using React, I'm going to be working off a simple tutorial app called ReactBox (repo). The one page app implements basic CRUD functionality for ideas without requiring a page refresh.

In the app, I may have a list of ideas. Each idea has a title and a body. In addition to CRUDing ideas, we want our user to be able to search through their ideas by title and body text. So for example, if I type "awesome" into a textbox, I want to see only my ideas with the word "awesome" somewhere in the title or body.

Search Component

React is based on building small components that encompass bits of functionality. If you clone the above repo, you'll see that our app has three components:

  1. CreateIdeaForm.js - contains the functionality for creating an idea, which we save in local storage.
  2. IdeaList.js - contains the functionality to iterate through ideas.
  3. Idea.js - contains the functionality to render a single idea on the page.

At the top of our component hierarchy is our App.js file.

Let's start by adding a search component, which will be a search field that updates the state of our app. Note, I use className to apply style. They aren't necessary for basic functionality.

touch src/components/Search.js

// src/components/Search
import React from 'react';

class Search extends React.Component {
  render() {
    return (
      <div className="row">
        Hello World!
      </div>
    )
  }
}

export default Search;

The above component is just going to show a "Hello World!" message when we add it to our app. Let's add it to the app, just to make sure everything is wired up correctly. Notice that in addition to adding the <Search /> component, we also have to import the component at the top of our App.js file.

// App.js

import React from 'react';
import CreateIdeaForm from './components/CreateIdeaForm'
import IdeaList from './components/IdeaList'
import Search from './components/Search'

class App extends React.Component {
 ...
  render() {
    return (
      <div className='container'>
        <CreateIdeaForm saveIdea={ this.storeIdea.bind(this) }/>
        <Search />
        <IdeaList ideas={this.state.ideas}
                  destroy={this.destroyIdea.bind(this)}
                  updateTitle={this.updateTitle.bind(this)}
                  updateBody={this.updateBody.bind(this)}/>
      </div>
    );
  }

And when we reload the app, we see our hello message:

Now that we see our search component, it's time to make it actually a text input. So we'll go into our Search component and write:

// src/components/Search
import React from 'react';

class Search extends React.Component {
  render() {
    return (
      <div className="row">
        <div className="input-field">
          <label>Search</label>
          <input type="text"/>
        </div>
      </div>
    )
  }
}

export default Search;

And now when we look at the page, we have a beautiful search field:

Of course, the search field is pretty, but it currently does not do anything. Let's build our search functionality. We are going to want our search to refresh the idea list every time the user triggers a key up event. Key up events mean that when the user is typing in the search field a event is triggered each time a new key is released. This will make the search feel snappy and mean that we will not need a search button.

// src/components/Search
import React from 'react';

class Search extends React.Component {

  handleSearch(event) {
    console.log("we sure are handling that search....")
  }

  render() {
    return (
      <div className="row">
        <div className="input-field">
          <label>Search</label>
          <input type="text" onKeyUp={this.handleSearch.bind(this)}/>
        </div>
      </div>
    )
  }
}

export default Search;

Now when we look at our app, and start typing in the search field, we should see the message "We sure are handling that search...." in our console which tells us that the onKeyUp event is triggering our handleSearch function. Notice how the message is logged nine times below because I typed nine letters into my search and each key up fires off the handleSearch function.

We can register a change to the search field, but now we need to figure out what is in the search field. To do so, we'll need to examine the event object that is automatically passed into our handleSearch function. The event has a target property, which refers to the element from which the event was triggered. In this case, the target is the entire search field. In jQuery when we have an input element and want to get its value, we call $('selector').val(). In React we call .value. Let's log the contents of the search field on keyup now.

// src/components/Search
import React from 'react';

class Search extends React.Component {

  handleSearch(event) {
    console.log(event.target.value)
  }

  render() {
    return (
      <div className="row">
        <div className="input-field">
          <label>Search</label>
          <input type="text" onKeyUp={this.handleSearch.bind(this)}/>
        </div>
      </div>
    )
  }
}

export default Search;

This is fun, but we are still not searching. What we want to really do next is send the query we get from our search field back up to our App. We want to go into our App.js file because our search component is a sibling of the idea list. With React, my understanding is that messages get passed from child to parent component, but not between siblings. So our search component should not change the state of its sister component IdeaList.

We'll add a searchIdeas function to our app and then pass it into the Search component as a property called search Ideas. In the code below "searchIdeas" is the property name. We then use curly brackets to pass in the function searchIdeas. We call searchIdeas from the object 'this' because the function belongs to our App. I think of it in pseudo code as doing App.searchIdeas. We are also binding this to the function call, because when the function is invoked we want to be able to access all the properties and state of the App. I think understanding the notion of 'this' is the toughest part of React.

// App.js
  searchIdeas(query){
    console.log("Our App knows the query: " + query)
  }

  render() {
    return (
      <div className='container'>
        <CreateIdeaForm saveIdea={ this.storeIdea.bind(this) }/>
        <Search searchIdeas={this.searchIdeas.bind(this)}/>
        <IdeaList ideas={this.state.ideas}
                  destroy={this.destroyIdea.bind(this)}
                  updateTitle={this.updateTitle.bind(this)}
                  updateBody={this.updateBody.bind(this)}/>
      </div>
    );
  }

Now back in our search component, we'll pass our query string up to the searchIdeas function in our App. To access the searchIdeas function we pass into the Search component, we need to use the props property on the Search component. We'll do so by writing:

handleSearch(event) {
    this.props.searchIdeas(event.target.value)
  }

Here 'this' is our Search component. We created our Search component with the property "searchIdeas". This property points to the searchIdeas function in our App and expects a string argument.

If everything is wired up, we should still be logging the query on key up, but the logging is happening in the app, not the handleSearch function in our Search component.

Next we need to actually search our ideas. To do so we are going to need to understand a little more about how ideas are actually stored in this application. We are using local storage to hold the ideas so that when a user comes back to the page or refreshes they still can see their ideas. 

// App.js
  componentDidMount(){
    const ideas = JSON.parse(localStorage.getItem('ideas')) || []
    this.setState({ ideas: ideas, allIdeas: ideas})
  }

Our App.js component has a componentDidMount function, which is part of React. This function is automatically run just before the component is rendered on the page. In this function we are saying go into local storage, find the ideas and store them in the state of the application. We are storing them twice, first as the ideas property of our App's state and second as the allIdeas property. I think this makes getting all the ideas back on the page once a user is finished searching pretty simple.

Let's add the following code to our searchIdeas function.

// App.js
  searchIdeas(query){
    let ideas = this.state.allIdeas.filter((idea) => {
      return idea.title.includes(query) || idea.body.includes(query)
    });
    console.log(ideas)
  }

In the above code, we are creating a variable called ideas. We are setting ideas to equal a filtered list of allIdeas stored in state. We are filtering for title or body text that includes our search query. Next we are logging these ideas to the console.  When I test out my search with the query '4' I should see all of the ideas with the string '4' in either the title or the body logged to console:

Notice how there is one object in the ideas array that is logging to console. That is because only one of my ideas has the string '4' in either the title or the body. If I type 'great' I get three ideas logged to console because three ideas have the word 'great' in the title or body.

We are filtering! Now the only thing left to do is update the page so only ideas that match our search criteria are visible. This is where React really shines. In jQuery I'd have to hide or show each idea, but React just does that for us. All we need to do is update the state of our App and the entire App will render any new changes. It is magic.

searchIdeas(query){
    let ideas = this.state.allIdeas.filter((idea) => {
      return idea.title.includes(query) || idea.body.includes(query)
    });
    this.setState({ideas: ideas})
  }

The this.setState function automatically re-renders the app on the page for us. Now when I search for the word 'great' I will only see the ideas with that string in their title or body.

The change to the page feels instantaneous.

Final thoughts

React is such an interesting technology. I'm really enjoying learning it and getting a better understanding of the advantages it offers. Thanks for following along!