Author avatar

Manujith Pallewatte

Managing Actions of the Child Components in React

Manujith Pallewatte

  • Jul 30, 2019
  • 9 Min read
  • 183 Views
  • Jul 30, 2019
  • 9 Min read
  • 183 Views
Web Development
React

Introduction

React is all about unidirectional data flow. When you pass down props from your parent component to the child components, you essentially flow your data down the hierarchy. Deep inside, React utilizes this flow to control the way components react to changes. But, as in any real-world application, the need arises for a child components to flow data up the hierarchy. In this guide, we will explore the basics of that while touching some of the unusual circumstances that you may encounter as well.

We are building a simple search component for a product listing. The requirement is simple: when a user types an input value into the search and clicks the search button, we display the list of items matching the string value. For this guide, we will be breaking the app into three components.

1
2
3
App.js
    |- SearchInputComponent.js
    |- SearchResultComponent.js

Naturally, we will be storing the global state of the application in the App component. Any data/actions required by child components will be flown down the hierarchy. The following code shows how the App component is structured. The full source is available in the Github Repo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// App.js

import React from 'react';
import SearchInputComponent from './SearchInputComponent';
import SearchResultComponent from './SearchResultComponent';

const listing = [
  "Product A",
  "React",
  "Java",
  "Table with chair"
]

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

    this.state = {
      results: listing
    }
  }

  doSearch = (query) => {
    console.log(query);
    //come complex search goes here
  }

  render(){
    return (
      <div>
        <h2>Product Search</h2>
        <SearchInputComponent />
        <SearchResultComponent />
      </div>
    )
  }
}

export default App;
javascript

The Natural Flow

Firstly, we need to send the filtered list of products to the SearchResultComponent, which we pass down through a prop. Next, we need to make sure that we get the correct update from the SearchInputComponent when a user types something. So, we decide to handle the input state from the parent component (App) and create the corresponding event handler in it. To use the event handler, we again pass it down as a prop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// App.js

// ...

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

    this.state = {
      results: listing,
      query: ""
    }
  }

  doSearch = (query) => {
    console.log(query);
    //come complex search goes here
  }

  onQueryChange = (txt) => {
    this.setState({
      query: txt
    })
  }

  render(){
    return (
      <div>
        <h2>Product Search</h2>
        <SearchInputComponent
          onInputChange = {this.onQueryChange}
          query={this.state.query}
        />
        <SearchResultComponent
          results = {this.state.results}
        />
      </div>
    )
  }
}

export default App;
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SearchResultComponent.js

import React from 'react';

class SearchResultComponent extends React.Component{
  render(){
    return (
      <div>
        <ul>
            {this.props.results.map((result, i) => (
                <li key={i}>{result}</li>
            ))}
        </ul>
      </div>
    )
  }
}

export default SearchResultComponent;
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SearchInputComponent.js

import React from 'react';

class SearchInputComponent extends React.Component{
  render(){
    return (
      <div>
        <input 
            type="text"
            value={this.props.query}
            onChange={(e) => { this.props.onInputChange(e.target.value) }}
        />
      </div>
    )
  }
}

export default SearchInputComponent;
javascript

As you can observe, this is the ideal use of natural or one-way data flow. App passes down the update function onQueryChange() to SearchInputComponent and the input inside the SearchInputComponent, in turn, sends the user input data to the update function as a parameter. The flow is always unidirectional.

This approach works for most use cases in real-world applications. If you find your application constantly struggling in adhering to the pattern, you have an issue in the way you have structured the state in your application. Although it seems cumbersome at first, taking time to get used to this pattern saves time in the long run. Lifting State Up from the React documentation is a great place to start, if you want to dive deep into it.

However, sometimes we find situations where we need to override this unidirectional flow. These instances include:

  1. Managing focus, text selection or media playback options
  2. Triggering animations
  3. Accessing third party DOM libraries (ex: jQuery)

Reference: Refs and the Dom.

For these situations, we use Refs.

React Refs

Refs provide a way to access DOM nodes or React elements created in the render method.

Refs, as the name suggests, are a reference to a DOM node (HTML or React) in the current DOM. While the most common use case for the use of Ref is interactions with third party libraries, we will focus on how it can be used to modify child components outside the natural data flow.

Let's assume that, rather than passing down the event handler for the input change, we want our App component to directly fetch the text input value from the child component on the click of a button. This is necessarily an overcomplication of the scenario for the guide. So, the idea is to create a function in the SearchInputComponent which will return the current text value of the input. We will also use Ref to grab the value of the input inside the SearchInputComponent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// SearchInputComponent.js

import React from 'react';

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

      this.inputRef = React.createRef();
  }

  getInputValue(){
      return this.inputRef.value;
  }

  render(){
    return (
      <div>
        <input 
            type="text"
            ref={this.inputRef}
        />
      </div>
    )
  }
}

export default SearchInputComponent;
javascript

In the modified SearchInputComponent above, you can observe how we have used a Ref to reference the input element. In the getInputValue() function, we access this element just like we are accessing a DOM element in Javascript (Note that the current property gives the access to the instance of the element). This is one way of using Ref.

Now we’ll modify App to collect the input value from SearchInputComponent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// App.js

// ...

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

    this.state = {
      results: listing,
      query: ""
    }

    this.childRef = React.createRef();
  }

  doSearch = (query) => {
    console.log(query);
    //come complex search goes here
  }

  onSearchClick = () => {
    const query = this.childRef.current.getInputValue();
    console.log(query);
  }

  render(){
    return (
      <div>
        <h2>Product Search</h2>
        <SearchInputComponent
          onInputChange={this.onQueryChange}
          ref={this.childRef}
        />
        <button
          onClick={this.onSearchClick}
        >Search</button>
        <SearchResultComponent
          results = {this.state.results}
        />
      </div>
    )
  }
}

export default App;
javascript

Here, we are creating a Ref to the SearchInputComponent which is a React component than an HTML element. Since we are accessing the current instance of it, we also get access to all of its methods and instance variables. If you run the above code, you can observe that, on the click of the button, App can access the input value successfully.

Note
At first, you may be intrigued by the idea of Refs. Especially if you are coming from a vanilla javascript background. This feels like having better control over the elements. But overusing Ref is highly discouraged in the React practice. It essentially overrides the natural data flow and can have unexpected side effects (when coupled with third party DOM manipulation libraries). Also, an important thing to remember is that a function component can NOT be referred to using Refs. Since they don't possess a current instance, as the Class components do, you can't reference them.

Conclusion

In this guide, we briefly looked at the idea of unidirectional data flow in React. We then explored the need for React Refs through a superficial example. Refs essentially provide a by-pass mechanism for the natural data flow. While it has its uses, it's highly discouraged to overuse Refs in your application.

0