DEV Community

ian-a-frankel
ian-a-frankel

Posted on

Passing Information in React

Introduction

In this post I will discuss what I recently learned about the JavaScript React library. While working on previous projects, I had few experiences understanding how different javascript files interacted with each other.

By chance I learned that two scripts simultaneously operating on the same DOM can be aware of each other's variables if they are both imported directly into the html file, so some information is passed through the DOM. However, in an ideal world, a developer should want to understand and control exactly what information each file is aware of.

Prelude: States

One of the key features of React is that instead of directly manipulating the DOM and using JavaScript event listeners, it instead causes the page, or components of the page, to re-render when the states of relevant variables are changed.

For this we import the useState hook from react, which allows us to create variables whose values can only be changed by calling the corresponding setter functions. Many things can be controlled by a single state.

React Components

The main building blocks of a project in React are called components. A common convention is to put each component is in its own .js file, and pairs of files communicate via import and export commands. I'm not sure if this is strictly necessary, but in all applications I am aware of we arrange components in a hierarchy whereby the parent imports functions that are exported by the child.

A react component is formatted with almost everything except the import and export commands inside the body of a function. When we set the function to return a value expressed in JSX format, it returns html elements for rendering. We won't discuss all of the details of the syntax here, but if we have a function called Header with properly specified JSX return values, it may be called as by a parent, and DOM elements may be created according to the value returned by the Header function.

Here is some sample from a React component, which I modified as an exercise:

(Source for all code in this post: https://github.com/ian-a-frankel/react-hooks-information-flow-lab)


//App.js

import React, { useState } from "react";
import ShoppingList from "./ShoppingList";
import itemData from "../data/items";
import Header from "./Header.js"

function App() {

  const [isDarkMode, setIsDarkMode] = useState(false);

    function onDarkModeClick() {
      setIsDarkMode((isDarkMode) => !isDarkMode)
    }

  return (
    <div className={"App " + (isDarkMode ? "dark" : "light")}>
      <Header isDarkMode={isDarkMode} onDarkModeClick={onDarkModeClick}/>
      <ShoppingList items={itemData} />
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

We will discuss the meaning of all parts of the code, first starting with import and export.

Import and Export

The export command at the end tells us that the App function, which is in some sense the body of the file, can be imported and called in another file. The import commands tell us what information is being imported from other files; the relative paths of imported files must be specified. In particular, we see 'Header.js' and 'ShoppingList.js' are in the same directory as 'App.js', while 'items' is in the data directory which shares a parent directory with 'App.js'. Header and ShoppingList are functions that App calls.

Passing Props

The functions Header and ShoppingList do not have access to the variables in App by default. Instead, we specify which information the children functions can access by passing them as props.

Functions in JavaScript are a type of object, and we are simply specifying some of their key-value pairs.

In order for the ShoppingList function to access this information, we need to specify it as an argument, which we do here:


//ShoppingList.js

function ShoppingList({ items }) {

Enter fullscreen mode Exit fullscreen mode

For reasons beyond the scope of this post, props are wrapped in braces and passed as objects, and then destructured back into variables in their initial form. The value for the key 'items' was specified in App.js when ShoppingList was called.

We note that the function ShoppingList must be exported in order for it to be imported into App.js.

States and Rendering

So far we have seen how a function that is called can be fed information from a parent, but how does it actually influence the values of variables in the parent? The answer is that we call the functions that have been passed as props. Let's take a look at some of the code from the Header component, whose output contains a button element that toggles Dark Mode and Light mode when clicked. The text of the button is conditional on whether the state of the 'IsDarkMode' is true or false:


function Header({isDarkMode, onDarkModeClick}) {

    return (
        <header>
        <h2>Shopster</h2>
        <button onClick={onDarkModeClick}>
          {isDarkMode ? "Dark" : "Light"} Mode
        </button>
      </header>
    )
}

export default Header

Enter fullscreen mode Exit fullscreen mode

However, we can see that this is not the only thing that is changed when the state of 'IsDarkMode' changes. The return value of App contained the following element:

<div className={"App " + (isDarkMode ? "dark" : "light")}>

Enter fullscreen mode Exit fullscreen mode

The state variable does not just affect the elements that it is passed to as a prop. This div element is rendered with CSS styling, and the styling depends on the class of the html element. Because the function 'onDarkModeClick' that was passed as a prop has scope up to the App component, interacting with its child the Header component can affect elements in App which are outside of Header. In this way we see information is communicated back up the component hierarchy.

Disadvantage: Burden of Passing Props

In a large program, there can be many layers of nesting between components and it is a lot to remember which props are passed where. We can bundle together props in a 'context' object with React's createContext hook, and then access them in a descendant with the useContext hook, potentially skipping many intermediate components. However, all React components in the DOM that use a context must be children of a context-providing "provider" DOM element. These additional steps typically only justify themselves when a prop has to be passed many times, so we do not include an example here.

Top comments (0)