Nested Components
Nested Components are exactly what the name implies. They are children nested inside parent components. They simply help us create more complex UI structures. For example, an alert component probably has another sub-component nested within it to indicate the actual number of alerts. A message box has an inner input element and a send button.
Nested components simplify complex applications. In this coding challenge, let’s build a truck wheel manager application. I followed this modular approach to nest wheels inside axles, and nest axles within the truck frame (see image to the right.) Both wheels and axles could then be dynamically added or deleted from the UI, offering flexibility in defining different types of vehicles (trailer, semi, bus, etc.)
Understanding how props travel through nested components to the innermost children is important. Without redux (global component state management), one of the obvious things you might do is simply keep passing your props down the entire component hierarchy (through component constructors,) eventually landing at the element in which props data needs to be rendered.
Try not to think of nested react components as something more than what they really are. Nesting in one form or another, is a common pattern across most web development languages. In CSS you “cascade” (or nest) styles. In HTML, you nest tags. In React, you nest components. It’s simply a modular way of thinking about your code.
Coding Challenge Ahead
Let’s practice React Nested Components by creating a complex UI scaffold for truck axle and wheel management software.
First, Let’s Practice Creating A Vanilla React Component
This tutorial will not make much sense without knowing how to create plain vanilla React components.
This subject is already covered in create basic react components tutorial on this site.
Recall how we usually create simple vanilla components in React:
var Vehicle = React.createClass( { render: function() { return() } } );
That’s just a boring React component. In a real-life coding situation, you must understand how to create useful components.
By using the ES6 syntax — in particular the class keyword — we make our code look a lot cleaner and easier to maintain later down the road. You might not think so at first, but things change as your react application’s source code footprint starts to grow.
class Vehicle extends React.Component {
constructor(props) {
super(props);
this.state = { axles: [], wheels: [] };
}
// The render function shares the same scope with the constructor
render() {
// Use ES6 destructuring to get properties from state and props objects
const { axles, wheels } = this.state;
const { editable } = this.props;
return (<Frame />)
}
}
Type the code above into the box below to learn react and receive Emoji awards!
0% completed
Structuring Nested Components
Congratulations! You just practiced the creation of Vehicle component, the container encompassing the whole enchilada.
Any component you create can be your primary application scaffold. Usually it’s called Application or App.
Here we used Vehicle but it can be anything you want.
We also learned how to “destructure” object properties into individual variable names (axles, wheels, editable) from the upper level components’s state and props object. These variables are now readily available for access from within the render method in which they were destructured.
A few words about destructuring { … } object properties
What is destructuring? It’s just a cleaner way of saying:
let name = this.props.name; let value = this.state.value;
It allows us to “destructure” multiple properties into multiple variable names, without having to rewrite heaps of ugly redundant code.
Destructuring allows us to simplify the statements above as:
let { name, value } = this.state;
Now that looks a lot cleaner! What happened is name and value properties attached to state object are now available for access as variable names in the scope in which they were destructured.
It is recommended to use destructuring in your react components. But, just to give you an idea of the differences, here is exactly the same component with and without destructuring:
Without Destructuring you end up using this.state directly:
With Destructuring you just poll for axles property:
class Vehicle extends React.Component { constructor(props) { super(props); this.state = { axles: [0, 1, 2] } } render() { return( <div>axles = { this.state.axles.length } </div> );} // close render scope }// close class scope
class Vehicle extends React.Component { constructor(props) { super(props); this.state = { axles: [0, 1, 2] } } render() { let { axles } = this.state; return( <div>axles = { axles.length }</div> );} // close render scope }// close class scope
Be sure to add Babel if you’re prototyping directly in the browser (just for testing and practicing the code, never for production build):
<script src = "https://unpkg.com/babel-standalone@6/babel.min.js"></script>
In real development environments setup for committing to the production server, you will install Babel from the command line instead. But for the demonstrational purpose of this tutorial, you can quickly add Babel, primarily so you can use JSX syntax in your experiments.
How To Properly Handle Nested Return Values From render Method
When I was learning React, I struggled with understanding how to properly return components from the render method. Let’s take a look at several examples that gradually build into more complex cases:
// Return an empty container (correct) return(<div></div>) // Return an empty container, using a self closing tag (correct) return(<div />) // Return a list of <div> elements using .map method (correct) return(<div> { this.state.axles.map((object, index) => { return(<div key = {index}>{ index }</div>) } ) } </div>) // Same as above, except using a self closing <div /> element (correct) return(<div> { this.state.axles.map((object, index) => { return(<div key = {index} />) } ) } </div>) // Same as above but cleaner syntax by avoiding second return keyword (correct) return(<div>{ this.state.axles.map((object, index) => <div key = {index} />) }</div>)
You can avoid using a second return keyword inside the iterator. This arrow function syntax, where you skip { brackets } automatically assumes that what goes after => will be returned, without having to use the return keyword. This is an ES6 feature.
Iterating Using Array.map Method
As far as mini-architecture of this UI component goes, roughly the following is what we’re trying to achieve:
Component scaffold:
b>After replacing with iterators (pseudo-code):
<Vehicle> <Frame> <Axle> <Wheel /> <Wheel /> </Axle> <Axle> <Wheel /> <Wheel /> <Wheel /> <Wheel /> </Axle> <Axle> <Wheel /> <Wheel /> <Wheel /> <Wheel /> </Axle> </Frame> </Vehicle>
<Vehicle> <Frame> this.state.axle[0].map((w, i) => { return <Axle key = { i } /> }); this.state.axle[1].map((w, i) => { return <Axle key = { i } /> }); this.state.axle[2].map((w, i) => { return <Axle key = { i } /> }); </Frame> </Vehicle>
The ES6 method .map is used here to iterate over all items [0, 1, 2] in the imaginary array this.state.axle;
When we write the actual application, these array objects will be replaced with something more meaningful than just a list of numbers. Perhaps each axle array can contain a list of wheels on that axle.
Every time you use .map iterator inside return you have to provide a key pointing to the iterable index (or similar unique ID).
Here is how map method (together with arrow function syntax) works in a nutshell:
this.state.iterable.map((object, index) => { return <Component idx = {index} obj = {object} /> })
Each one of the JSX-syntax components <Vehicle />, <Frame />, <Axle /> and <Wheel /> must be separately created in the same manner as we created class Vehicle component in the first coding challenge above. You can then store them in separate modules in their respectful files, for example: components/vehicle.js, components/axle.js, components/wheel.js etc.
In this example I hard coded the scaffold to support only 3 axles (we will actually no use this pattern.) I included it only as a mental model for what we’re trying to achieve, before going to the next step. Ideally, we want to have a dynamic number of axles. And that’s what we will create in the next coding challenge on this page.
Nesting Iterators
We want to iterate through all axles, not just hard code them. Within axle component we will iterate through all wheels attached to that axle. Thankfully, react makes it possible to nest iterators, too. Which makes our React code incredibly compact and highly reusable.
Let’s create the Wheels component first. It will contain definition of wheels per axle.
class Wheels extends React.Component { constructor(props) { super(props); this.state = { wheel: [0, 1]; } // store some wheels, just the indices for now } render() { // Destructure wheels property let { wheel } = this.state; // Iterate through available wheels [0, 1] return(<div>{ wheel.map((w, index) => <div key = {index}>{ index }</div> }</div>); } }
Now that we have a <Wheels /> component that iterates over the number of wheels stored in it (in this case just the two iterable placeholder values [0, 1]) let’s insert it into this.state.axles iterator, which will iterate over Wheels component we just created. All of this is happening within the Frame component we’re going to create below:
class Frame extends React.Component { constructor(props) { super(props); this.state = { axles: [0, 1, 2]; } // store some axle indices (later can be objects) } render() { // Destructure axles property let { axles } = this.state; // Iterate through available axles [0, 1, 2] return(<div>{ axles.map((obj, index) => <Wheels key = {index} axle = {obj} /> }</div>); } }
Now we iterate through entire axle array from Frame component’s state and within it, we iterate through all the wheels on that axle in <Wheel /> component’s render method. We have just successfully nested one iterator within another, effectively giving us the ability to dynamically render axles and wheels associated with them, regardless of their number.
Note that return statement always expects to return some content inside one single wrapper container.
You cannot return a side-by-side pair of elements as in:
// Generates an error (you cannot return a side-by-side pair of elements): return(<div></div><div></div>); // Correct (single wrapper container) return(<div>Hello there.</div>);
This can be a nuisance in a lot of cases, because it breeds empty container components, where you otherwise may not need one.
You can do the following self-closing syntax with success:
// This is allowed return(<Axles />); // You can pass props to self-closing components, as to any other component: return(<Axles idx = { index } string = { "text" } />);
Putting It All Together
So far we’ve explored how to build out the main application scaffold, and created several unique components. Let’s put the bits and pieces together. The following example is actually a living breathing React application working directly from this page. It features everything we learned in this tutorial. You can see the source code of this page to see how it works:
// Add required libraries <script crossorigin src = "https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src = "https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script src = "https://unpkg.com/babel-standalone@6/babel.min.js"></script> Note that we need to specify script as text/babel type in order to use JSX tags <script type = "text/babel"> // Container style let container_style = { margin: "auto", width: 130 }; // Axle container style let axle_style = { padding: 4, height: 110 }; // Truck blocks let truck_block = { position: "absolute", top: -25, left: 300 }; // Wheel container style (a fairly lengthy definition!) let wheel_style = { fontFamily: "verdana", fontSize: 12, float: "left", margin: 2, padding: 3, width: 17, height: 50, textAlign: "center", background: "black", borderRadius: 7, color: "white", writingMode: "vertical-rl", textOrientation: "mixed" }; // Wheel container component class Wheels extends React.Component { constructor(props) { super(props); } render() { return( <div> { this.props.axle.map((wheel, index) => { return(<div key = { index } style = { wheel_style }>{ this.props.axle[index] }</div>) }) } <div style = { truck_block }> { /* Find correct image based on the axle index */ } <img src = { "truck" + (this.props.index == 0 ? 1 : 2) + ".png" } /> } </div> </div>); } } // Axle component class Axle extends React.Component { constructor(props) { super(props); } render() { return(<div style = { axle_style }> Axle = { this.props.index } <div>{ <Wheels index = { this.props.index } axle = { this.props.axles[this.props.index] } /> }</div> </div>); } } // Application container class Vehicle extends React.Component { constructor(props) { super(props); this.state = { axles: [ ["A","B"], ["C","D","E","F"], ["G","H","I","J"] ] } } render() { let { axles } = this.state; return( <div style = { container_style }> { this.state.axles.map((axle, index) => <Axle key = { index } index = { index } axles = { axles } />)}</div>); } } // Initialize application on the "root" element ReactDOM.render(<Vehicle />, document.getElementById("root")); </script>
Just copy and paste that into your HTML page, and add the following application container:
<div id = "root"></div>
Executing the React script will automatically place the results of the render method of the Vehicle component into provided application container <div id = “root”></div> element.
Comments In React
You’ll find that standard // comments break your react code. Instead the solution is to encapsulate your comments inside brackets as shown in the following example.
<div style = { truck_block }>
{ /* Find correct image based on the axle index */ }
<img src = {"truck" + (this.props.index == 0 ? 1 : 2) + ".png"} />
</div>
Note that the truck part is dynamically rendered depending on axle index value. Axles under index 0 (there is only one) will render the truck cabin. Axles whose index is greater than 0 will render the trailer box. The ternary JavaScript operator is used to provide an elegant solution for determining the correct truck image per axle.
That’s all there is to it! React code is usually clean and performant, considering all the tasks it accomplishes.