Using React subcomponents is a practice of using multiple components in a component to break down a complex hierarchy into small, independent, and reusable components. These subcomponents can be configured to handle events and data via props. “Props” stands for properties, and are a React feature that passes data between components as an immutable key-value pair object.
Implementing subcomponents is quite easy, but sometimes it can be tricky to retain the context with the use of anonymous functions or React classes. This guide covers the implementation of subcomponents with anonymous and ES6 features in React.
A React class component can be created by extending the Component
class, and an onClick
event can be implemented with a function to handle the click:
1import React, { Component } from 'react';
2
3export default class App extends Component {
4
5 handleClick() {
6 console.log("click");
7 }
8
9 render() {
10 return (
11 <div className="container">
12 <button onClick={this.handleClick} > click </button>
13 </div>
14 );
15 }
16}
In the above snippet, the handleClick
function will be triggered whenever the button
is clicked. There is no need to bind handleClick
with this
because handleClick
is not accessing anything from its container (App
) component. Otherwise, there would have been an error, because this
points to the window
object at runtime, not App
.
A subcomponent can be implemented either as a class
or a functional component. The implementation can be optimized using functional components. The Address
subcomponent will receive the details via props
from the container component:
1function Address({ type, houseNo, clickHandler }) {
2 const isPermanent = type && type === 'Temporary';
3
4 return (
5 <div>
6 <b>{type}</b>
7 <p>{houseNo}
8 {' '}
9 {isPermanent ? <button onClick={clickHandler} title='Delete'>X</button> : ''}
10 </p>
11 </div>
12 )
13}
The Address
component is using the destructuring assignment { type, houseNo, clickHandler }
to access the passed prop
values directly. The clickHandler
is a function to handle clicks, which is being passed to the onClick
attribute.
The App
component is using state
to store the data of addresses, and the removeTempAddress
function will reset the value of the addresses
object with only the permanent
object:
1export default class App extends Component {
2
3 // set the values for the addresses in state object
4 state = {
5 addresses: {
6 permanent: {
7 houseNo: 'ABC',
8 },
9 temporary: {
10 houseNo: 'XYZ',
11 }
12 }
13 }
14
15 // an arrow function to retails the context of this
16 removeTempAddress = () => {
17 const { permanent } = this.state.addresses;
18 // reset value of 'addresses' to 'permanent' only
19 this.setState({ addresses: { permanent } })
20 }
21
22 render() {
23 const { permanent, temporary } = this.state.addresses;
24 return (
25 <div className="container">
26 <Address type='Permanent' houseNo={permanent.houseNo} />
27 {temporary && <Address type='Temporary' houseNo={temporary.houseNo} clickHandler={this.removeTempAddress} />}
28 </div>
29 );
30 }
31}
Passing event-handlers via props gives control to the container component to handle the events and helps to create reusable components.
Generating subcomponents at runtime is a common requirement for dynamic UI. Often the UI has to render/update as per the changes in the list of data items, for example, displaying items in a cart, tabular data for stocks, etc. Due to the dynamic size of the data, the solution is to iterate through data while generating the items.
Using the array map
is a common method to iterate data values for processing. It takes an anonymous function to process the elements of an array, and an arrow function can be used to access the context (App
component) inside an anonymous function:
1export default class App extends Component {
2 state = {
3 items: ["Banana", "Mango"],
4 };
5
6 handleClick(event) {
7 console.log(event.target.innerHTML);
8 }
9
10 render() {
11
12 var listItems = this.state.items.map((item, index) => {
13 return <li key={index} onClick={this.handleClick}><a href='/#'>{item}</a></li>;
14 });
15
16 return (
17 <div className="container">
18 <ul>{listItems}</ul>
19 </div>
20 );
21 }
22}
The use of the arrow function provides access to this.handleClick
by preserving the context. Alternately, the map
function also takes this
as an argument to preserve context:
1var listItems = this.state.items.map(function (item, index) {
2 return <li key={index} onClick={this.handleClick}><a href='/#'>{item}</a></li>;
3}, this); // this as second argument to map function
This can also be done using bind
:
1var listItems = this.state.items.map(function (item, index) {
2 return <li key={index} onClick={this.handleClick}><a href='/#'>{item}</a></li>;
3}.bind(this)); // binding this with anonymous function
Subcomponents are a great way to create reusable elements and to pass events and data via props. At runtime, by default this
refers to the window
object, and there are many ways to preserve the context in dynamic subcomponents via an arrow function, overloaded methods to pass this
, and the bind
method. Hopefully, this guide explained the necessary details to create subcomponents in different ways. Happy coding!