Module 2 - Component Composition

JavaScript Modules

In this Core Competency, you will explore the powerful combination of Native ES Modules (the standard format to package JavaScript code for reuse) and React, unlocking new possibilities in component composition.

Native ES Modules provide a modern and standardized way to organize and share JavaScript code, offering enhanced modularity and encapsulation. You will learn how leveraging Native ES Modules can significantly improve the development experience when building React components, enabling seamless composition, reusability, and maintainability.

Harness the full potential of Native ES Modules and discover their indispensable role in the world of React component composition!

Importing and exporting components

So far, you have been working with multiple files and languages in a single project: CSS, HTML, JS. There are multiple ways to make these languages work together. You can insert CSS and JS code directly into the HTML file using <style> and <script> tags. And you can also link external files using keywords like link, href, and src.

In React, consistency is key, since one of the major advantages of this architecture is to write reusable code. To be able to focus on the development of components and modularize your application, you will learn another way of linking code: export and import JS code from one file to another.

But first, let's talk about the history of JS development.

History

In the first days of JavaScript, inserting JavaScript into a web application meant writing the code inside a script tag inside an HTML file. The script ran sequentially, that is, from top to bottom. Back then, if you wanted to use the same code in another project, you had to copy and paste it.

There were also performance issues. Functions and variables were all global, and if you weren't careful, you could quickly see them holding unexpected values. Eventually, the <src> attribute allowed for more reusability but was still dependent on order, and the code was still globally scoped.

Server side JS

In 2009, Node.js was released and allowed for JavaScript to execute outside of the browser.

With the adoption of a common JavaScript-specified standard library known as CommonJS, developers now have defined new ways for sending data to and from our file systems. These advances were a complete game-changer for the JavaScript community, but all still relied heavily on third-party bundlers and transpilers to address common issues. Tools like Webpack and Rollup would compile packs of modules and assign any missing dependencies before sending them off to the browser. Transpilers such as Babel handle translating source code for the browser and convert the latest features of ES6 ( any not supported in the browser) into compatible ES5. While handy, these programs require downloading, updating, and configuring.

JS modules (ECMAScript modules, or ES modules for short)

This brings us to today!

ES modules — the first standardized syntax for using modules in JavaScript. Where past methods relied on specific functions or third-party libraries, you could export functions, data, and components from your files by merely prefixing the export keyword. Then, when you want to bring such external features into your file, you use the import keyword, the name, and the location of the exported item. And that's it, no dependencies or configurations; everything is ready to go right out of the box!

How to Build It

Let's go through some examples. Start up a new React app and follow along.

Open your React project in the editor and create a new directory under frontend, called modules. Inside this new directory, create a new file named module.js.

Named exports

Inside of Module.js, add this function:

const emphasize = (str) => {
  return str.toUpperCase();
};
export emphasize;

The code above is exposing the emphasize function to any other JS file that wants to import it. You could also use the inline notation like this:

export const emphasize = (str) => {
  return str.toUpperCase();
};

Now go back to the App.js file and change it to:

import React from 'react';
import {emphasize} from '../modules/module.js'

export default function App() {

  return (
    <div>
      {emphasize('this used to be lower caps')}
    </div>
  );
};

You should have the modified string rendered. Check the browser to make sure it's working!

Here's what changed in the code.

First, you added another import statement declaring that the emphasize function should be imported from a particular file called module.js (located one directory level up (frontend), inside the modules folder). As a developer, you should know that emphasize is a function, but emphasize could be any type of object in JS, like arrays or strings.

You added a line inside the <div>, using curly brackets (because it is JSX), indicating that you wanted to render the return value of the function.

Default exports

You can have multiple 'named exports' in a single module and import each one of them by name/location, as you did above.

But you can also have a single 'default export', which makes the import process a little bit different, perhaps easier, and more flexible. If you edit your module.js file like this:

export default function emphasize(str) {
  return str.toUpperCase();
};

You can change the App.js file to import the function like this:

import emphasize from '../modules/module.js'

Since emphasize now is the default export from the module, you don't need to use curly braces in the import statement to specify which of the exports you want to bring in. In fact, you can use any name for the import, since it's the default:

import ToUpper from '../modules/module.js'

You can mix and match named and default imports in a single statement. For instance, the App.js file could be modified to import both objects from the react.js module like this (the .js extension is optional):

import React, {useState} from 'react'; // importing default and named imports at the same time
import ToUpper from '../modules/module.js'

The first one (React) is the default, the other one (useState) is one of the many named exports from the react module. Additionally, you can import multiple named exports using object destructuring inside the curly braces:

import { item1, item2, item3 } from './directory/items.js'

The prefixing ./ on the file URL points to the current directory, while ../ would refer to the parent directory, and so on.

This pattern is incredibly efficient, and you get the added bonus of top-level variables not polluting the global object.

Exports in React

Now that you know how to work with exports, let's talk about exporting actual React components and rendering them inside other React components.

As you probably noticed, your React project initializes the App component as a default export function. So far, we have never really explained why, but you can now understand that:

  • The App.js file is an ES module
  • More than that, it's also a React component (a function returning JSX)
  • And this function is the only object exposed, as a default export.

Now if you look inside the index.js file, you will find the corresponding import statement:

import App from './components/App'

As a general rule, you will not be messing with index.js to import and render additional components. Instead, you will add children components to the App component by creating new component files, exporting the function as a default export, and importing it in the App.js file. This may sound confusing, so let's break this down in code.

First, create a new React component. In a new file under the components folder, named Paragraph.js, add this content:

import React from 'react';

export default function Paragraph() {
  return (
    <p>Just a paragraph!</p>
  );
};

Now, edit the App.js file to import the new component, and render it inside the App component:

import React from 'react';
import Paragraph from './Paragraph' // importing the Paragraph function

export default function App() {

  return (
      <div>
        <Paragraph /> // this is the React syntax for calling/rendering the Paragraph component
      </div>
  );
};

And there you go! Just keep in mind:

  • When creating a new React component, you must import React from 'react'; in the same file
  • The parent component which will render the child component must import the child component
  • The JSX syntax to render a component (or the returned HTML of the component function, if you will) is <ComponentName />. This is called component nesting, and you will learn more about it later.

Here's another important consideration. The JSX code must have one, and only one root element.

RIGHT:

  return (
      <div>
        <h1>Header</h1>
        <Paragraph />
      </div>
  );

Only one root <div>. The Header and the Paragraph are siblings inside the root parent.

RIGHT:

  return (
      <div>
        <div>
           <h1>Header</h1>
        </div>
        <Paragraph />
      </div>
  );

Only one root <div>. The parent has two children, and the <div> child has one child (header).

WRONG:

  return (
     <h1>Header</h1>
     <Paragraph />
  );

Two siblings at the root. You should add a parent <div> encompassing everything to make it work.

Conclusion

The React architecture strongly encourages the use of ES modules.

If you follow the standard practice of creating a separate file for each component, every React component is in fact an ES module that will be eventually imported by its parent.

You have also read how to render a component inside the JSX of another component. This is the fundamental approach of building with React: a hierarchy of components that are conditionally rendered whenever there's a change in state.

Building a UI with React

In this Core Competency, you will learn the powerful concept of props in React, which allows for seamless information flow and dynamic behavior within the component tree. Props serve as a mechanism for passing data (and callbacks) from parent components to child components, enabling effective communication and composition.

In React, when you need to pass information held on state inside one component to another, you pass them as "props". "Props" is a naming convention used to refer to objects passed to another component, just like function arguments.

Passing data using props

A stateful component is one that holds state data, by using the useState hook inside the function. This state might be relevant only to that "owner" component and thus never has to be passed down to other components. But often, a particular component will need to communicate with its children, passing down "props" that will define how the children will be rendered.

As mentioned before, "props" are just arbitrary data passed as an argument to the component (which is a function after all). As you know, there's a special syntax for calling (or rendering) components from inside another component's JSX. That means that the syntax for passing props is also a bit different from how we do it with regular functions. But you'll get there soon!

The Importance of the App Design / Layout

When designing your application, you should layout components in a tree, defining which ones should have state, and which ones need to receive state from other components. This will help you decide where to exactly place state and which components will receive state in the form of props.

As mentioned before, the React architecture is sometimes restrictive in what you can do as a developer, not only to avoid errors and make it easier to debug and maintain code, but also to ensure that renders will occur precisely. For instance, you already know that state should not be modified directly, but instead by its corresponding function.

One thing to remember about props is that you never change props data! Props are read-only "copies" of a particular state, or any other data created and sent by the parent. In other words, the component receiving the data should use it to implement its internal logic, but not modify it directly. If the child component receiving props needs to change its local state, that's perfectly fine.

Let's see some examples on how and why to use props.

How to Build It

Let's take the following example of data you'll be passing from the App component to a child component, a simple user object holding two key/value pairs:

const someUser = { name: "Hubert", age: 27 };

First, edit the App.js file to include local state, which will be later passed down as props (the app will crash, and this is ok because you haven't created the other components yet):

import React, { useState }  from 'react';
import UserInfo from './UserInfo'

export default function App() {
  const [someUser, setUser] = useState({ name: "Hubert", age: 27 });

  return <UserInfo user={someUser} />;
};

Remember when we briefly discussed about component nesting in the previous Core Competency!? Well, when working with props, nesting is intrinsic. Props are always sent from one component to another child component, which is rendered (or "called") inside the parent's JSX. This nesting is also related to "component composing": the aspect of app development concerned with designing the component tree, and deciding which blocks of code can be re-used and packed into a new component.

Notice how the App component is not only rendering the UserInfo component (which has been properly imported), but defining an "attribute-like" property user, with a value of {someUser}. This is the React syntax for passing props!

Let's reinforce that: the syntax for sending props to a child component is adding "attributes" to the Component tag, like in this example:

return <UserInfo user={someUser} />;

The JSX above is calling the UserInfo component, and passing the user prop with a value of someUser.

You can pass as many "attributes" as you like, each one containing any arbitrary data. React will create and pass down a props object containing all the data. The code above is effectively translated to: "add the user key to the props object, which value is someUser data, pass down the props object to the UserInfo component and have it render itself."

In this case, the final props passed down to UserInfo contains a single entry:

{ user: { name: "Hubert", age: 27 } }

Now create the UserInfo component in a new UserInfo.js file under the components folder:

import React from 'react';
import DisplayName from './DisplayName'

export default function UserInfo(props) {
  return (
    <div>
      <DisplayName user={props.user} />
    </div>
  )
}

Notice how the function is now declared to receive an argument (the props), which you can call whatever you prefer. For this example, and in most cases, you'll be using the name props to maintain consistency.

Also, see how the UserInfo component is in turn rendering, or calling, the DisplayName component, which is also receiving data. UserInfo could send any kind of data to DisplayName, including the result of some calculation, or any local state. But in this case, it is destructuring the received props object to send:

{ user: { name: "Hubert", age: 27 } }

In the end, UserInfo is just forwarding data from App to DisplayName.

Create the DisplayName component in a new file:

import React from 'react';

export default function DisplayName(props) {
  return (
    <div>
      <h2>Hello, my name is {props.user.name}</h2>
    </div>
  )
}

The above code block should render "Hello, my name is Hubert".

Props are a key element to React development, and helps the developer in many ways:

  • Control. Keeping state in one of a few select components and passing as props minimize the risk of making unintended changes to state data.
  • Readability. Separate files are easy to keep in order and make it easier for other developers to read through the code, know where our state is held and where it's being sent.
  • Maintenance. If you want to make changes, you can find and edit components quickly. Working in files that only manage one or two different aspects of the application is a much easier task than scrolling through hundreds of lines of code! You can also isolate problems and debug faster.
  • Reusability. This is huge! Now you have reusable components, and they can render any data that you pass through, as long as it matches props object expected by the component.

This would be a good time to experiment with the React Dev Tools for Chrome! Open the Developer Tools and click on the 'Components' tab. You will see the tree with all the nested components. Click on a component to inspect its state and props!

Composing React components

You already know how to create local state, nest components and pass props down the component tree, and that is the core of every React application.

Often, you can improve your code by packing code into a new component. This makes your application more modular, and this comes with many advantages. Modular components allow you to re-use components, simplify your code, and more easily debug issues. As a rule of thumb, it's generally better to have more components, with each one of them being very direct and simple. Having fewer and more complex components will decrease the readability and maintenance of the code.

How to Build It

In this Learning Objective, you don't need to edit your files, since the examples are very straight forward. But feel free to test them in your live development server if you want to!

Consider this code:

function Hello(props) {
  return (
    <div>
      <h2>Hello world from, {props.name}</h2>
      <div>
        <h4>My best friend in this world is: {props.bestFriend}</h4>
        <p>My favorite book is: {props.favoriteBook}</p>
      </div>
    </div>
  );
}

This component is doing some simple rendering of DOM elements. It only relies on three props, so it's not super sophisticated, but it makes for some cumbersome programming.

Whenever you notice that your JSX is getting to large and hard to read, you should consider creating and nesting a new component!

So let's start by breaking out the inner DOM elements whose purpose is to render some data into its own component:

function AboutMe(props) {
  return (
    <div>
      <h4>My best friend in this world is: {props.bestFriend}</h4>
      <p>My favorite book is: {props.favoriteBook}</p>
    </div>
  );
};

You can now use this new component inside the Hello JSX, and forward props one level down:

function Hello(props) {
      return (
        <div>
          <AboutMe bestFriend="Homer Hickam" favoriteBook="October Sky"/>
        </div>
      );
}

Notice how the string props are not wrapped in curly braces. React can render either a string literal, as shown above, or an expression wrapped in curly braces.

Now our component looks a little cleaner, but we can take it one step further. Notice that the <p> tag could also be it's very own component:

function Book(props) { <p>My favorite book is: {props.favoriteBook}</p>;}

And to use it nested within the AboutMe component

function AboutMe(props) {
  return (
    <div>
      <h4>My best friend in this world is: {props.bestFriend}</h4>
      <Book favoriteBook={props.favoriteBook} />
    </div>
  );
};

This goes to shows that React can pass props down as far as you'd like. However, we recommend not nesting components super deep, the trick is to find a good balance between re-usable and readable code!

Finally, let's explore a common case where you may want to conditionally render a nested component:

function AboutMe(props) {
  return (
    <div>
      <h4>My best friend in this world is: {props.bestFriend}</h4>
      {props.favoriteBook? <Book favoriteBook={props.favoriteBook} /> : null}
    </div>
  );
};

In the code above, the Book component is only rendered if AboutMe has received the corresponding prop. Otherwise, the result of the expression will be null and nothing will happen. This pattern of using ternary operators to conditionally render components is very common and can avoid the rendering of incomplete information when you don't need to render it.

Passing callbacks using props

While designing your component tree, you will probably end up in a scenario where most of the application state is located up the tree, while some of the components down the tree are just rendering information computed locally, with or without props.

App state is usually located up the tree because higher-level components typically send down data (props) to multiple children. Of course, you want to avoid repeating state inside children components and deal with unnecessary copies of the same data. But if a child component needs to change state from a parent (which happens often), how do you go about that? Remember, you can't change props, but even if you could, doing so would not change the original state of the parent. So how do you build components that can update the parent's state?

But if a child component needs to change state from a parent, which happens often, how do you go about that? Remember, you can't change props. But even if you could, the original state of the parent would not be changed. How to access data that is not in the local scope?

Тhe solution is to access the parent's state through a callback function, which is received as a prop! Let's explore this design pattern.

How to Build It

Let's create a simple app combining all the concepts you have learned so far! Create and edit your files like this:

App.js

import React from 'react';
import Game from './Game';

export default function App() {
  return <Game player="Ninja86" />;
};

The App component is calling the Game component, passing a player string as props.

Game.js

import React, {useState} from 'react';
import ScoreBoard from './ScoreBoard';

export default function Game(props) {
const {player} = props;
const [score, setScore] = useState(0);
const increase = () => {
  setScore(score+1);
}
  return (
    <div>
      <ScoreBoard player={player} score={score} />
      <button onClick={increase}>Increase</button>
    </div>
  )
};

The Game component is configured to receive props and extract the player from props. Notice the destructuring notation used, which is equivalent to const player = props.player.

A local state is created to keep track of the score, and the corresponding increase() function is defined to increase the score through setScore().

The ScoreBoard component is called with two props: the player data received as a prop from the parent and the score data from local state.

Additionally, a Button is created to call increase() on clicks. Test the application and verify that the button is working.

ScoreBoard.js

import React from 'react';

export default function ScoreBoard(props) {
const {score, player} = props;

return (
  <div>
     <h3>
       {player} is at {score}
     </h3>
  </div>
  )
};

The ScoreBoard component has no local state, it is just rendering the props.

So far, so good!

Now, following the composing design pattern, you could create another component and have the Game component call it instead of hard-coding <button onClick={increase}>Increase</button>. Why? Well, you might want to call another function, perhaps decrease(). Or you might want to create another interactive element, other than a button. This just gives you more flexibility by decoupling components.

Let's create a new file called ScoreChanger.js:

ScoreChanger.js

import React from 'react';

export default function ScoreChanger(props) {
const {increase, decrease} = props;

return (
  <div>
    <button onClick={increase}>Increase</button>
    <button onClick={decrease}>Decrease</button>
  </div>
  )
};

This component is now focused on changing the score, and doesn't care about accessing and displaying the actual numbers. But you have something new now. Тhe props are receiving actual functions! And again, the component is not even aware of the implementation of the functions.

You don't need to edit the App.js or the ScoreBoard.js files, only the parent Game.js:

Game.js

import React, {useState} from 'react';
import ScoreBoard from './ScoreBoard';
import ScoreChanger from './ScoreChanger';

export default function Game(props) {
const {player}= props;
const [score, setScore] = useState(0);

const increase = () => {
  setScore(score+1);
}
const decrease = () => {
  if (score > 0) {
    setScore(score-1);
  }
}
  
return (
  <div>
    <ScoreBoard player={player} score={score} />
    <ScoreChanger increase={increase} decrease={decrease} />
  </div>
  )
};

Game.js now has a definitions for the decrease() function (making sure that the score can't be negative!) and the code passed both functions as props. Beautiful! Your Game component is responsible for managing the local state and implementing the functions, while the children (ScoreBoard and ScoreChanger) have very specific and decoupled roles.

Conclusion

In JS, any object (e.g., arrays, strings, functions) is a first-class object and can be passed down as props. By sending callbacks to children components, you can design a very modular application that is easy to upgrade and maintain (this is "separation of concerns:" in action!).

Once you have the patterns down, you're just writing JavaScript.

At this point, there's not much more you need to understand about state and props. That's it! A solid initial design will tell you where to use local state and what type of props should be sent to which children.

Next, you will learn about another essential hook in React: useEffect.

Module 2 Project: Composing React Components

For this project, you'll build a simple Instagram clone using React. Instead of connecting to a database or server, you will be using file of mock data that is stored within the project itself. Each object in the mock data represents a faux Instagram post. Your React application will receive this faux post data and render each as a separate Instagram post.

The module project contains advanced problems that will challenge and stretch your understanding of the module's content. The project has built-in tests for you to check your work, and the solution video is available in case you need help or want to see how we solved each challenge, but remember, there is always more than one way to solve a problem. Before reviewing the solution video, be sure to attempt the project and try solving the challenges yourself.

Instructions

The link below takes you to Bloom's code repository of the assignment. You'll need to fork the repo to your own GitHub account, and clone it down to your computer:

Starter Repo: Composing React Components

  • Fork the repository,
  • clone it to your machine, and
  • open the README.md file in VSCode, where you will find instructions on completing this Project.
  • submit your completed project to the BloomTech Portal

Solution

Additional Resources