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.
1App.js
2 |- SearchInputComponent.js
3 |- 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// App.js
2
3import React from 'react';
4import SearchInputComponent from './SearchInputComponent';
5import SearchResultComponent from './SearchResultComponent';
6
7const listing = [
8 "Product A",
9 "React",
10 "Java",
11 "Table with chair"
12]
13
14class App extends React.Component{
15 constructor(props){
16 super(props);
17
18 this.state = {
19 results: listing
20 }
21 }
22
23 doSearch = (query) => {
24 console.log(query);
25 //come complex search goes here
26 }
27
28 render(){
29 return (
30 <div>
31 <h2>Product Search</h2>
32 <SearchInputComponent />
33 <SearchResultComponent />
34 </div>
35 )
36 }
37}
38
39export default App;
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// App.js
2
3// ...
4
5class App extends React.Component{
6 constructor(props){
7 super(props);
8
9 this.state = {
10 results: listing,
11 query: ""
12 }
13 }
14
15 doSearch = (query) => {
16 console.log(query);
17 //come complex search goes here
18 }
19
20 onQueryChange = (txt) => {
21 this.setState({
22 query: txt
23 })
24 }
25
26 render(){
27 return (
28 <div>
29 <h2>Product Search</h2>
30 <SearchInputComponent
31 onInputChange = {this.onQueryChange}
32 query={this.state.query}
33 />
34 <SearchResultComponent
35 results = {this.state.results}
36 />
37 </div>
38 )
39 }
40}
41
42export default App;
1// SearchResultComponent.js
2
3import React from 'react';
4
5class SearchResultComponent extends React.Component{
6 render(){
7 return (
8 <div>
9 <ul>
10 {this.props.results.map((result, i) => (
11 <li key={i}>{result}</li>
12 ))}
13 </ul>
14 </div>
15 )
16 }
17}
18
19export default SearchResultComponent;
1// SearchInputComponent.js
2
3import React from 'react';
4
5class SearchInputComponent extends React.Component{
6 render(){
7 return (
8 <div>
9 <input
10 type="text"
11 value={this.props.query}
12 onChange={(e) => { this.props.onInputChange(e.target.value) }}
13 />
14 </div>
15 )
16 }
17}
18
19export default SearchInputComponent;
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:
Reference: Refs and the Dom.
For these situations, we use 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// SearchInputComponent.js
2
3import React from 'react';
4
5class SearchInputComponent extends React.Component{
6 constructor(props){
7 super(props);
8
9 this.inputRef = React.createRef();
10 }
11
12 getInputValue(){
13 return this.inputRef.value;
14 }
15
16 render(){
17 return (
18 <div>
19 <input
20 type="text"
21 ref={this.inputRef}
22 />
23 </div>
24 )
25 }
26}
27
28export default SearchInputComponent;
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// App.js
2
3// ...
4
5class App extends React.Component{
6 constructor(props){
7 super(props);
8
9 this.state = {
10 results: listing,
11 query: ""
12 }
13
14 this.childRef = React.createRef();
15 }
16
17 doSearch = (query) => {
18 console.log(query);
19 //come complex search goes here
20 }
21
22 onSearchClick = () => {
23 const query = this.childRef.current.getInputValue();
24 console.log(query);
25 }
26
27 render(){
28 return (
29 <div>
30 <h2>Product Search</h2>
31 <SearchInputComponent
32 onInputChange={this.onQueryChange}
33 ref={this.childRef}
34 />
35 <button
36 onClick={this.onSearchClick}
37 >Search</button>
38 <SearchResultComponent
39 results = {this.state.results}
40 />
41 </div>
42 )
43 }
44}
45
46export default App;
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.
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.