in

React: Component Nesting, Re-usability & Configuration

React Logo

Intro

One of the beautiful things about React is that the page is built up using multiple reusable components. These components can be nested inside of each other, reused several times, and can also be individually configured. For example a button. All of the buttons will mostly look the same, however they will link to different places and probably have different text on them.

Let’s start by taking a look at a good use case for components. Let’s say we are rendering a comments section. We are going to be rendering the same thing over and over again. In this example we are using Semantic UI for styling and the Faker module to generate random data.

import React from "react";
import ReactDOM from "react-dom";
import Faker from "faker";

const App = () => {
  return (
    <div className="ui container comments">
      <div className="comment">
        <a href="/" className="avatar">
          <img alt="avatar" src={Faker.image.avatar()} />
        </a>
        <div className="content">
          <a href="/" className="author">
            {Faker.name.firstName()}
          </a>
          <div className="metadata">
            <span className="date">Yesterday @ 10AM</span>
          </div>
          <div className="text">{Faker.lorem.sentences()}</div>
        </div>
      </div>
      <div className="comment">
        <a href="/" className="avatar">
          <img alt="avatar" src={Faker.image.avatar()} />
        </a>
        <div className="content">
          <a href="/" className="author">
            {Faker.name.firstName()}
          </a>
          <div className="metadata">
            <span className="date">Yesterday @ 10AM</span>
          </div>
          <div className="text">{Faker.lorem.sentences()}</div>
        </div>
      </div>
      <div className="comment">
        <a href="/" className="avatar">
          <img alt="avatar" src={Faker.image.avatar()} />
        </a>
        <div className="content">
          <a href="/" className="author">
            {Faker.name.firstName()}
          </a>
          <div className="metadata">
            <span className="date">Yesterday @ 10AM</span>
          </div>
          <div className="text">{Faker.lorem.sentences()}</div>
        </div>
      </div>
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#root"));

And all that gives us this:

This is so repetitive, we want to have a component for each comment!

Creating Reusable Components

  • Identify JSX that appears to be duplicated
  • What is the purpose of that block of JSX? Think of a descriptive name for what it does.
  • Create a new file to house this new component – it should have the same name as the component.
  • Create a new component in the new file, paste the JSX into it.
  • Make the new component configurable by using React’s ‘props’ system

Create New File

We identify the repetitive block which would be each individual comment, then in src we create a new file that describes this element.

and we paste in our repetitive block

import React from "react";

const CommentDetail = () => {
  return (
    <div className="comment">
      <a href="/" className="avatar">
        <img alt="avatar" src={Faker.image.avatar()} />
      </a>
      <div className="content">
        <a href="/" className="author">
          {Faker.name.firstName()}
        </a>
        <div className="metadata">
          <span className="date">Yesterday @ 10AM</span>
        </div>
        <div className="text">{Faker.lorem.sentences()}</div>
      </div>
    </div>
  );
};

export default CommentDetail;

Note that we export the component at the end to make it available in the rest of the app. Then we have to make this component configurable using Props.

Connect Component to Index

Because we have exported the component to it’s own file/module, we can now import it into our index.

import React from "react";
import ReactDOM from "react-dom";
import Faker from "faker";
import CommentDetail from './CommentDetail';

const App = () => {
  return (
    <div className="ui container comments">
      <CommentDetail />
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#root"));

And note that we have replaced the long string of JSX with just &lt;CommentDetail>. Components are the one exception in JSX where we don’t use the {} syntax to call a function.

And of course we could just repeat that multiple times to get several comments.

const App = () => {
  return (
    <div className="ui container comments">
      <CommentDetail />
      <CommentDetail />
      <CommentDetail />
      <CommentDetail />
      <CommentDetail />
    </div>
  );
};

Configuring Components With Props

Props are a system for passing data from a parent component to a child component. The goal is to customize or configure a child component. A child cannot pass data to a parent directly using the props system. Props is short for properties.

The syntax to provide props is very similar to how you would add any property to an HTML tag.

const App = () => {
  return (
    <div className="ui container comments">
      <CommentDetail author="Nick" />
      <CommentDetail author="David" />
      <CommentDetail author="Margaret" />
      <CommentDetail author="Sally" />
      <CommentDetail author="Balthazar" />
    </div>
  );
};

In this case author is the name of the prop, and "Nick" or the other names would be the value of the prop. Then if we move over to the CommentDetail component the prop is provided as the first argument to the function.

const CommentDetail = (props) => {
  console.log(props);
  return (
    <div className="comment">
      <a href="/" className="avatar">
        <img alt="avatar" src={Faker.image.avatar()} />
      </a>
      <div className="content">
        <a href="/" className="author">
          {Faker.name.firstName()}
        </a>
        <div className="metadata">
          <span className="date">Yesterday @ 10AM</span>
        </div>
        <div className="text">{Faker.lorem.sentences()}</div>
      </div>
    </div>
  );
};

And we can see that we have console.log’d the props there which gives us this in the console.

Then we replace our Faker name with the prop name props.author

const CommentDetail = (props) => {
  console.log(props);
  return (
    <div className="comment">
      <a href="/" className="avatar">
        <img alt="avatar" src={Faker.image.avatar()} />
      </a>
      <div className="content">
        <a href="/" className="author">
          {props.author}
        </a>
        <div className="metadata">
          <span className="date">Yesterday @ 10AM</span>
        </div>
        <div className="text">{Faker.lorem.sentences()}</div>
      </div>
    </div>
  );
};

And our Faker names have now been replaced by our Prop names in our component.

Multiple Props

To add additional props you just add additional properties to the component and then reference them in the component. Like so.

<CommentDetail author="Nick" timeAgo="3 Days Ago" />
<span>{props.timeAgo}</span>

So altogether that would be this.

const App = () => {
  return (
    <div className="ui container comments">
      <CommentDetail author="Nick" timeAgo="3 Days Ago" />
      <CommentDetail author="David" timeAgo="4 Days Ago" />
      <CommentDetail author="Margaret" timeAgo="10 Days Ago" />
      <CommentDetail author="Sally" timeAgo="15 Days Ago" />
      <CommentDetail author="Balthazar" timeAgo="40 Days Ago" />
    </div>
  );
};
const CommentDetail = (props) => {
  console.log(props);
  return (
    <div className="comment">
      <a href="/" className="avatar">
        <img alt="avatar" src={Faker.image.avatar()} />
      </a>
      <div className="content">
        <a href="/" className="author">
          {props.author}
        </a>
        <div className="metadata">
          <span className="date">{props.timeAgo}</span>
        </div>
        <div className="text">{Faker.lorem.sentences()}</div>
      </div>
    </div>
  );
};

We can mix in variables as well as strings into our props properties.

const App = () => {
  return (
    <div className="ui container comments">
      <CommentDetail
        author="Nick"
        timeAgo="3 Days Ago"
        content={Faker.lorem.sentences()}
        profilePic={Faker.image.avatar()}
      />
      <CommentDetail
        author="David"
        timeAgo="4 Days Ago"
        content={Faker.lorem.sentences()}
        profilePic={Faker.image.avatar()}
      />
      <CommentDetail
        author="Margaret"
        timeAgo="10 Days Ago"
        content={Faker.lorem.sentences()}
        profilePic={Faker.image.avatar()}
      />
      <CommentDetail
        author="Sally"
        timeAgo="15 Days Ago"
        content={Faker.lorem.sentences()}
        profilePic={Faker.image.avatar()}
      />
      <CommentDetail
        author="Balthazar"
        timeAgo="40 Days Ago"
        content={Faker.lorem.sentences()}
        profilePic={Faker.image.avatar()}
      />
    </div>
  );
};
const CommentDetail = props => {
  console.log(props);
  return (
    <div className="comment">
      <a href="/" className="avatar">
        <img alt="avatar" src={props.profilePic} />
      </a>
      <div className="content">
        <a href="/" className="author">
          {props.author}
        </a>
        <div className="metadata">
          <span className="date">{props.timeAgo}</span>
        </div>
        <div className="text">{props.content}</div>
      </div>
    </div>
  );
};

Nesting Components

To nest components inside of other components we turn the inside component into a prop of the parent component. Let us take a wrapper for each of these comments that is an approval card with two buttons.

import React from "react";

const ApprovalCard = (props) => {
  return (
    <div className="ui card">
      <div className="content"> COMMENT GOES HERE </div>
      <div className="extra content">
        <div className="ui two buttons">
          <div className="ui basic green button">Approve</div>
          <div className="ui basic red button">Reject</div>
        </div>
      </div>
    </div>
  );
};

export default ApprovalCard;

Then we go back to our index and we nest one of our comments inside of this Approval Card.


      <ApprovalCard>
        <CommentDetail
          author="Nick"
          timeAgo="3 Days Ago"
          content={Faker.lorem.sentences()}
          profilePic={Faker.image.avatar()}
        />
      </ApprovalCard>

However this will still not display the comment yet. If we console log the props of the Approval Card we can see why.

The Approval Card now has a property called children, so to reference this we would call props.children.

import React from "react";

const ApprovalCard = (props) => {
  console.log(props);
  return (
    <div className="ui card">
      <div className="content"> {props.children} </div>
      <div className="extra content">
        <div className="ui two buttons">
          <div className="ui basic green button">Approve</div>
          <div className="ui basic red button">Reject</div>
        </div>
      </div>
    </div>
  );
};

export default ApprovalCard;

With this method we can change the content inside of the Approval Card using the index instead of the CommentDetail.js component. That component will remain static and display whatever children we decide to put inside of it now.

React Logo

React: Getting Started With React and JSX

React Logo

React: Functional Components vs Class Components & Updating States