Skip to content

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

How to Implement Communication Between Independent Components in React.JS

Jan 16, 2020 • 15 Minute Read

Introduction

There are multiple ways to pass data between React components:

  • Render Props/Props
  • Context
  • React-Redux/Redux

This guide will focus on the different ways of communication between React components. In each of the methods, we'll want to get two-way communication:

  • Parent to Child
  • Child to Parent

Render Props/Props

Using props to pass data to and from React components is the most convenient way.

What is a prop? Our markup in components are defined as below:

      class Child extends React.Component {
    render() {
        return (
            <div>
                Child component
            </div>
        )
    }
}
class Parent extends React.Component {
    render() {
        return (
            <Child />
        )
    }
}
    

It would result in:

      Child component
    

Parent is the parent component of Child; Child is the child component of Parent, and Child is in fact rendered inside Parent. This is how Parent can render Child:

      class Parent extends React.Component {
    render() {
        return (
            <Child name="chrisps" />
        )
    }
}
    

Some additional things were added in component Child by Parent. It’s similar to adding an ID in HTML div, such as <div id=”d1”></div>. The ID attribute has a value chrisps. In fact, every element is an object of HTMLElement class.

The browser engine parses the above div tag and as a result, instances of HTMLElement are created for every tag. Every tag in HTML is associated with a corresponding HTMLElement:

      div -> HTMLDivElement
anchor -> HTMLAnchorElement
body -> HTMLBodyElement
button -> HTMLButtonElement
...
    

All these HTMLElements have a superclass HTMLElement. The methods and properties of these HTMLElements are used for rendering purposes and to manipulate data.

      HTML*Element
    - appendChild
    - attributes
    - getAttribute
    - hasAttribute
    - innerHTML
    - innerText
    - setAttribute
...
    

The above example would result in HTMLDivElement as shown below:

      const divElement = new HTMLDivElement()
divElement.setAttribute("name", "chrisps")
    

The props object will set the attribute name as shown below:

      props = {
    name: "chrisps"
}
    

This will get passed by React to the Child component class.

      class Child extends React.Component {
    constructor(props) { }
    ...
}
    

It will then enable access to the value of attributes for the Child component using this.props.name:

      class Child extends React.Component {
    constructor(props) {
        log(this.props.name) // chrisps
    }
    ...
}
    

As React is based on JSX, the compilation of Parent would result in this:

      <Child name="chrisps" />
    |
    |
    |
    |
    v
React.createElement(Child, { name:"chrisps" })
    

The first parameter to React createElement is the element to render. The second represents an object which has the element attributes. The output of the createElement is an object literal containing both these parameters. The object literal can be either an actual element or a class. React checks the object literal during the rendering process. If the object literal is a class, React instantiates the class using the keyword new passing in the props object and calls the render function.

      const objType = React.createElement(Child, { name:"chrisps" })
if(IS_ELEMENT(objType)) {
    //...
}
else {
    if(IS_CLASS(objType)){
        const klass = objType.component;
        // klass holds the Child class "class Child extends React.Component {...}"
        const props = objType.props;
        // props contains { name: "chrisps" }, the attributes passed to Child component in Parent component "<Child name="chrisps" />"
        const k = new klass(props);
        // here, it(klass) is instantiated and props is passed to it.
        k.componentWillMount();
        k.render();
        k.componentDidMount();
        //...
    }
}
    

The above code shows how React renders a component or an element. It should be noted that the code just shows how props arguments are received by the components. The first thing is to check whether objType is a class or an element. The IS_ELEMENT(…) method compares it with list of all HTML elements. If it is the same as one of the elements, it returns true, which indicates that the objType is an element. Otherwise it can be inferred that it is a class. If the objType is class, it destructures props and class from the objType. Then it creates an object of the class using the keyword new and passing in the props. Thus, we can see that component Parent has sent data to Child. How can Child send back data to Parent? It uses render props method to send data back to its parent Parent.

What is render props? It is a concept through which a function is passed to the child component as props. It can be string, object, number or array that is passed to the child component as props. Functions can also be passed as props in the following way:

      <Child func={() => {log("render props...")}} />
class Child extends React.Component {
    constructor(props){
        this.props.func()
    }
    render() {
        return (
            <div>
                {this.props.func()}
            </div>
        )
    }
}
    

In the above code, a function {} => {log{“render props”}} is passed to the Child component using a prop named func. The Child component can use this function through func in the props. As we know it is a function, we can call by adding (). We can then see render props logged to the console. Now that we are familiar with the working of render props, let's work on the child component and see how it can send data to the parent.

      class Parent extends React.Component {
    constructor() {
        this.output = this.output.bind(this)
    }
    output() {
        log("Hi there from React component !")
    }
    render() {
        return (
            <Child func={this.output} />
        )
    }
}
class Child extends React.Component {
    constructor(props) {}
    render() {
        return (
            <div>
                Child component
                <button onClick={this.props.func}>Send Data To Parent</button>
            </div>
        )
    }
}
    

The Parent component has a method bound to its instance. The use of bind(this) restricts JS to run the function within the scope of class, object or function. So, the function would still be able to access the properties and methods defined in the class Parent, even when running outside the class Parent. The output method is passed to component Child. It can be accessed by component Child through func property in the props. The Child component has a button, Send to Parent, with an associated onclick event set to call the output method. Now this.props.func in the component Child will call the output method in the component Parent:

      this.props.func === new Parent().output
    

The click on Send to Parent button in Child invokes this.props.func {this.props.func(evt)}, which eventually references the output method in Parent. The output function is run {output(evt)} and the following is printed on the screen:

      Hi there from React component !
    

The above line shows that Child component can now communicate to its parent. The code can be modified to send data from Child to its parent, Parent.

      class Parent extends React.Component {
    constructor() {
        this.output = this.output.bind(this)
        this.state = {
            count: 0
        }
    }
    output(evt) {
        log("Hi there from React component !")
        this.setState({count: this.state.count + evt})
    }
    render() {
        return (
            <div>
                <h1>Count: {this.state.count}</h1>
                <Child func={this.output} />   
            </div>
        )
    }
}
class Child extends React.Component {
    constructor(props) {}
    render() {
        return (
            <div>
                Child component
                <button onClick={(evt) => this.props.func(Math.random())}>Send Data To Parent</button>
            </div>
        )
    }
}
    

A state is added to Parent with a property count. When the button Send to Parent is clicked, a random number is generated and passed to this.props.func. The output method in Parent component gets the value in evt argument and updates the count state using this.setState(...). And that is how a child component can send back data to its parent. Thus, we can see that the parent can communicate to the child using props, and the child can communicate to the parent using render props. The parent state was changed by passing a function to the child component and calling that function inside the child. Render props and props have similar behavior; they only differ in their concept. Render props is used to send render logic between components. But in this case, it is used to send back data from the child component to the parent component.

Context

It is not a good practice to pass props to deeply nested components in a tree, as the result can be intimidating for most developers. There is a way to define our data globally and access it from anywhere in the app. That is through the use of Context and not via Redux or other similar implementation. In React, Context is used to share data between deeply nested components.

      const UserContext = React.createContext("cparker")
class App extends React.Component {
    render() {
        return (
            <UserContext.Provider>
                <Parent />
            </UserContext.Provider>
        )
    }
}
class Parent extends React.Component {
    render() {
        return (
            {this.context}
            <Child />
        )
    }
}
class Child extends React.Component {
    render() {
        return (
            <Sub-Child />
        )
    }
}
class Sub-Child extends React.Component {
    render() {
        <div>
            {this.context}
        </div>
    }
}
    

There are three components in a tree in the above code: App -> Parent -> Child -> Sub-Child. App renders Parent, Parent renders Child and Child renders Sub-Child. There is also a context defined named UserContext. Parent is defined within UserContext.Provider, which enables the Parent component children to access the data defined in the UserContext. The data defined in UserContext can be accessed by the children by {this.context}. Two attributes are required to be defined in the parent component in order to pass context from parent to rest of its children, i.e., childContextType and getChildContext. We need to define another attribute in the child to be able to access the context inside the child component. The parent can communicate data to its children by defining a context that defines data globally. Also how do the child components communicate data to the parent? Child components have the access to change the context data; this change will be visible globally across the nested components, including the parent component.

React-Redux/Redux

This functionality is similar to that of Context in React. Redux is a state management library that makes it easier to store your app state in one place and access it throughout the app. React-Redux was developed to make it easier to integrate Redux with React applications. As the state is stored in one place in the application, it is easier for the parent components to access and update it and for children components to capture these updates: Parent-to-Child communication. In a similar manner, child component can make changes in the state and parent components can capture these changes: Child-to-Parent communication.

      // ...
const initialState = {
    count: 0
}
const counterApp = function(state = initialState, action) {
    switch(action) {
        case 'INC_COUNT':
            return {...state, state.count++}
        return state;
    }
}
const store = createStore(counterApp)
// src/Parent.js
class Parent extends React.Component {
    render() {
        return (
            <button >Inc count!</button>
            <h1>Count: {this.props.count}</h1>
            <Child />
        )
    }
}
/**
* maps dispatch to props
**/
function mapDispatchToProps(dispatch) {
    return {
        updateCount: (count)=> { dispatch({type: "INC_COUNT",count})}
    }
}
/**
* maps redux state to props
**/
function mapStateToProps(state) {
    return {
        count: state.count
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(Parent)
// src/Child.js
class Child extends React.Component {
    render() {
        return (
            <button >Increment count</button>
            <h1>Count: {this.props.count}</h1>
        )
    }
}
/**
* maps dispatch to props
**/
function mapDispatchToProps(dispatch) {
    return {
        updateCount: (count)=> { dispatch({type: "INC_COUNT",count})}
    }
}
/**
* maps redux state to props
**/
function mapStateToProps(state) {
    return {
        count: state.count
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(Child)
    

A central store is created that has a count state. The parent component can update the count state and the child component will get that update. Also, the child component can make some changes in the count state and the parent component will receive it. Both the parent and child components are connected to the central store using a composition of React-Redux connect(…) function that returns a higher-order component.

Conclusion

Thus, we have seen how to communicate between React components and the ways we can achieve these two types of communication:

  • Parent-to-Child Communication
  • Child-to-Parent Communication

The use of props/render props enables the communication by using HTML-like attributes. The communication through Context is done by a central store, so that parent and child can simultaneously access and update. React-Redux/Redux works the same way as Context, the component read/write through a central store.