Have you ever wondered if we can only pass props down from the parent to the child in React? As data flow is unidirectional in React applications, then how would it be possible to update the state of the parent component from its child component?
Let’s take an example to understand the situation. I have developed a sample application which meets the above requirement. The example uses React with TypeScript.
In the example below, we have three components - “ParentLevel”, “ChildLevel1” and “ChildLevel2” - having a relationship of parent and child and grandchild components.
- import React, { Component } from 'react';
-
- interface IEmployee {
- id: number;
- name: string;
- username: string;
- email: string;
- isExpand: boolean;
- }
- interface IGroupWithEmploye {
- departmentName: string;
- employees: IEmployee[];
- }
-
- interface IStateForParentLevel {
- GroupWithEmployes: IGroupWithEmploye[];
- }
-
- export default class ParentLevel extends Component<{}, IStateForParentLevel>{
- public constructor(props: any, state: IStateForParentLevel) {
- super(props);
- this.state = {
- GroupWithEmployes: [] as IGroupWithEmploye[]
- }
- }
- componentDidMount() {
- var FileData = require('../../sampledata/EmployeeData.json');
- FileData.map(function (object: any, i: any) {
- object.employees.map(function (employee: any, j: any) {
- employee.isExpand = false;
- return employee;
- })
- return object;
- })
- this.setState({ GroupWithEmployes: FileData });
-
- var manageColumnSortingOrder = [{
- key: "id",
- Name: "ID",
- sortingOrder: "ASC",
- filterContent: ""
- },
- {
- key: "name",
- Name: "Name",
- sortingOrder: "ASC",
- filterContent: ""
- },
- {
- key: "username",
- Name: "User Name",
- sortingOrder: "ASC",
- filterContent: ""
- },
- {
- key: "email",
- Name: "Email",
- sortingOrder: "ASC",
- filterContent: ""
- }];
-
- }
- onCollapseExpandColumn(empId:number){
- console.log("call onCollapseExpandColumn");
- var initialRows = this.state.GroupWithEmployes;
-
- console.log(this.state);
- initialRows.map(function (department: any, i: any) {
- department.employees.map(function (emp: any, j: any) {
- if (emp.id == empId) {
- emp.isExpand = !emp.isExpand;
- }
-
- return emp;
- })
- return department;
- })
- this.setState({ GroupWithEmployes: initialRows });
- }
- render() {
- console.log(this.state.GroupWithEmployes);
- var ParentLevelThis=this;
- return (
- <table className="table table-striped" style={{ marginTop: 20 }}>
- <thead>
- <tr>
- <th>
-
- </th>
- <th>
- ID
- </th>
- <th>
- Name
- </th>
- <th>
- User Name
- </th>
- <th>
- Email ID
- </th>
- </tr>
-
- </thead>
- <tbody>
- {
- this.state.GroupWithEmployes.map(function (group, index) {
- return <ChildLevel1 onClickCallParentLevelMethod={ParentLevelThis.onCollapseExpandColumn.bind(ParentLevelThis)} department={group} key={index} ></ChildLevel1>
- })
- }
- </tbody>
- </table>
-
- )
- }
-
- }
-
- interface IChildLevel1 {
- department: IGroupWithEmploye;
- onClickCallParentLevelMethod:(empId:number)=>void;
- }
- class ChildLevel1 extends Component<IChildLevel1, {}>{
-
- onCollapseExpandColumn(empId:number){
- console.log("call ChildLevel1 onCollapseExpandColumn");
- this.props.onClickCallParentLevelMethod(empId);
- }
-
- render() {
- var ChildLevel1This=this;
- return (
-
- <React.Fragment>
- <tr>
- <td colSpan={5}>
- Department : {this.props.department.departmentName}
- </td>
- </tr>
- {
- this.props.department.employees.map(function (employee, index) {
- return <ChildLevel2 onClickCallChildLevel1Method={ChildLevel1This.onCollapseExpandColumn.bind(ChildLevel1This)} employee={employee} key={index} ></ChildLevel2>
- })
- }
-
- </React.Fragment>
- )
- }
- }
-
- interface IChildLevel2 {
- employee: IEmployee;
- onClickCallChildLevel1Method:(userId:number) => void;
- }
- class ChildLevel2 extends Component<IChildLevel2, {}>{
-
- onCollapseExpandColumn(empId:number){
- this.props.onClickCallChildLevel1Method(empId);
- }
- render() {
- return (
- <React.Fragment>
- <tr>
- <td><a className="collapseExpand" onClick={()=>this.onCollapseExpandColumn(this.props.employee.id)} >
- <i className={(this.props.employee.isExpand ? "fas fa-caret-down" : "fas fa-caret-right")}></i>
- </a></td>
- <td>{this.props.employee.id}</td>
- <td>{this.props.employee.name}</td>
- <td>{this.props.employee.username}</td>
- <td>{this.props.employee.email}</td>
- </tr>
- {
- (this.props.employee.isExpand) && (
- <tr>
- <td> </td>
- <td colSpan={4}>
- <table>
- <tbody>
- <tr>
- <td>ID</td>
- <td> {this.props.employee.id}</td>
- </tr>
- <tr>
- <td>Name</td>
- <td> {this.props.employee.name}</td>
- </tr>
- <tr>
- <td>User Name</td>
- <td> {this.props.employee.username}</td>
- </tr>
- <tr>
- <td>Email</td>
- <td> {this.props.employee.email}</td>
- </tr>
- </tbody>
- </table>
- </td>
- </tr>)
- }
- </React.Fragment>
- )
- }
- }
We render “ChildLevel1” component from the “ParentLevel” component and “ChildLevel2” component from the “ChildLevel1” component.
As per our requirement, we want to update the “ParentLevel” component’s state onClick of the Expand/Collapse button icon which is rendered in “ChildLevel2” component.
- In “ChildLevel2” Component, we have called “onCollapseExpandColumn” method of the same component on click of Expand/collapse icon.
- In “ChildLevel2” Component on “onCollapseExpandColumn” method, we have triggered “ChildLevel1” component’s method using props(this.props(“this.props.onClickCallChildLevel1Method” ))
- In “ChildLevel1”, we have bound “onCollapseExpandColumn” method so it gets triggered from “ChildLevel2” component’s method.
- Same as on “onCollapseExpandColumn” method’s call in “ChildLevel1”, we have called “ParentLevel” component’s method using
this.props (this.props.onClickCallParentLevelMethod(empId); )
- In “ParentLevel” component, we have bound “onCollapseExpandColumn” method so it is triggered from “ChildLevel1” component’s method.
- In “onCollapseExpandColumn” method of “ParentLevel” component, we have updated the state.