React: Props

It has been (another) brutally hot August in Texas. The other day I went to a neighborhood pool around the corner from me to try and cool down, but even the pools around here have been sitting in the sun and the heat for so long that the water was just tepid and unpleasant to be in. I've spent a ton of time indoors – more than makes me comfortable – but it has let me focus on things I might not have otherwise. I'm writing some new music and making a ton of progress on my first platinum run of Tears of the Kingdom.

Baking has slowed down a bit. If I'm going to use the oven, I try and do so first thing in the morning to try and keep the house as cool as possible in the afternoon and evening, when things are at their hottest. This makes timing hard, but it's not impossible. Last weekend I made a pair of saffron cardamom pound cakes that were delicious, and I'm still making bread here and there.

I also went to Chicago two weeks ago, for Sprout's annual on-site, Midyear Meetup. It was a whirlwind three-and-a-half-ish days, flying in on Monday morning, spending all of Tuesday and Wednesday working, socializing, connecting, strategizing, synergizing, interfacing, etc., and then flying out first thing on Thursday morning. Here is a pic of me that my coworker and friend Susan Densa took of me at a happy hour in the golden hour.

Joey in front of a graffiti wall at a bar in Chicago wearing an orange shirt.

Anyway, today I'm going to dig into a topic I've touched on a few times in the last few blog posts, React props.

What are Props?

Props are pieces of information that you pass to a JSX tag. They look similar to HTML attributes, but where HTML attribute values are typically strings, numbers, and booleans, you can pass any valid JavaScript value to a JSX tag, including arrays, objects, and functions.

Props are generally thought of almost entirely in relationship to React components, but in reality, just about any information you pass to any JSX tag is a prop. For example, any time you use className on any JSX element, like a <div>, to give it some classes, you're using a prop!

<div className="wide-open-spaces">
  className is a prop!
</div>

Similarly, when you use an img tag in JSX and pass it src or alt attributes, you're doing so using props.

<img
  src="joey.png" // this is a prop
  alt="Joey in front of a graffiti wall at a bar in Chicago wearing an orange shirt." // this is also a prop
/>

That being said, the most common pattern for prop usage is in conjunction with React components to make those components as flexible and reusable as possible.

Passing Props to a Component

As always, I think it's easiest to have a working example where we can see this code come together piece by piece. So in that spirit, let's build up a little Profile card component. Here is what the Profile card looks like:

To build this in React, let's start with a Profile.js component:

// Profile.js
import styles from "./Profile.module.css";

export default function Profile() {
  return (
    <div className={styles.profile}>
      <div className={styles.avatarWrapper}>
        <img
          src="https://joeyreyes.dev/static/d85cacc8111340043302c48b5f2db40e/5f169/joey.webp"
          alt="Joey in Vale, Colorado."
          className={styles.avatar}
        />
        <div className={styles.onlineStatus} />
      </div>
      <div className={styles.clearFix} />
      <h1>Joey</h1>
      <p>
        I'm a Web Developer in Austin, Texas and I've been writing web code for
        a long, long time.
      </p>
      <p>Joey's interests:</p>
      <ul>
        <li>Coding</li>
        <li>Music</li>
        <li>Baking</li>
      </ul>
    </div>
  );
}

We'll import this component into App.js and call it like so:

// App.js
import "./App.css";
import Profile from "./components/Profile";

function App() {
  return (
    <div className="App">
      <body>
        <div className="wide-border" />
        <Profile />
      </body>
    </div>
  );
}

export default App;

I won't dig too deep into styling, but the global App.css styles look like this:

/* App.css */
body {
  font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue",
    Helvetica, Arial, "Lucida Grande", sans-serif;
  font-weight: 300;
  background-color: #38473e;
}

And Profile.module.css looks like this:

/* Profile.module.css */
.profile {
  background-color: #fff;
  border-radius: 8px;
  max-width: 200px;
  padding: 20px;
  margin: 20px auto;
}

.avatarWrapper {
  float: right;
  position: relative;
  width: 200px;
}

.avatar {
  max-width: 60px;
  height: auto;
  width: 100%;
  border-radius: 100%;
  z-index: 2;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.onlineStatus {
  width: 65px;
  height: 65px;
  border-radius: 100%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
}

.active {
  background-color: green;
}

.inactive {
  background-color: red;
}

.clearFix {
  clear: both;
}

Check out React: Styling for more info on CSS Modules.

At this point, everything is hardcoded to this specific Profile.js file, but we can start to change that by passing the pieces of data to the Profile component like so:

// in App.js:
<Profile name="Joey" />

We've simply passed in the string "Joey" as the name prop when we call the Profile component. We can do the same for the bio sentence, too:

// in App.js:
<Profile
  name="Joey"
  bio="I'm a Web Developer in Austin, Texas and I've been writing web code for a long, long time."
/>

We can also pass numbers and booleans:

// in App.js:
<Profile
  name="Joey"
  bio="I'm a Web Developer in Austin, Texas and I've been writing web code for a long, long time."
  online={true}
/>

As well as more complex values, like arrays and objects:

// in App.js:
<Profile
  name="Joey"
  bio="I'm a Web Developer in Austin, Texas and I've been writing web code for a long, long time."
  online={true}
  avatar={{
    src: "https://joeyreyes.dev/static/d85cacc8111340043302c48b5f2db40e/5f169/joey.webp",
    alt: "Joey in Vale, Colorado.",
  }}
  interests={[
    "Coding",
    "Music",
    "Baking"
  ]}
/>

Reading props from a component

So with all of the different pieces of info that we want to include for our Profile card passed to the Profile component as props, we need to actually read them within Profile.js and render them.

The props Parameter

The first thing we can do in Profile.js is include a parameter named props in the function definition:

// Profile.js
import styles from "./Profile.module.css";

export default function Profile(props) {
  console.log(props);
  return (
    // ...
  );
}

When we console.log(props), we get the following:

{
  "avatar": {
    "src": "https://joeyreyes.dev/static/d85cacc8111340043302c48b5f2db40e/5f169/joey.webp",
    "alt": "Joey in Vale, Colorado."
  },
  "bio": "I'm a Web Developer in Austin, Texas and I've been writing web code for a long, long time.",
  "interests": [
    "Coding",
    "Music",
    "Baking"
  ],
  "name": "Joey",
  "online": true,
}

It's all of our data – cool. Now we can start plugging that data into our JSX expression slots:

// Profile.js
import styles from "./Profile.module.css";

export default function Profile(props) {
  return (
    <div className={styles.profile}>
      <div className={styles.avatarWrapper}>
        <img
          src="https://joeyreyes.dev/static/d85cacc8111340043302c48b5f2db40e/5f169/joey.webp"
          alt="Joey in Vale, Colorado."
          className={styles.avatar}
        />
        <div className={styles.onlineStatus} />
      </div>
      <div className={styles.clearFix} />
      <h1>{props.name}</h1>
      <p>{props.bio}</p>
      <p>Joey's interests:</p>
      <ul>
        <li>Coding</li>
        <li>Music</li>
        <li>Baking</li>
      </ul>
    </div>
  );
}

So far I've replaced <h1>Joey</h1> with <h1>{props.name}</h1> and <p>I'm a Web Developer in Austin, Texas and I've been writing web code for a long, long time.</p> with <p>{props.bio}</p>. But accessing name and bio through dot notation is kind of annoying. There has to be a better way.

Destructuring Props

That better way is to destructure the props. Since props is just an object, we can destructure the object properties by name. We could do so like this:

// Profile.js
import styles from "./Profile.module.css";

export default function Profile(props) {
  const { name, bio } = props;
  return (
    // ...
  );
}

And then it's just a matter of modifying our JSX to be <h1>{name}</h1> and <p>{bio}</p> instead of <h1>{props.name}</h1> and <p>{props.bio}</p>. We can also take it one step further and destructure within the parameter definition:

// Profile.js
import styles from "./Profile.module.css";

export default function Profile({ name, bio }) {
  return (
    <div className={styles.profile}>
      <div className={styles.avatarWrapper}>
        <img
          src="https://joeyreyes.dev/static/d85cacc8111340043302c48b5f2db40e/5f169/joey.webp"
          alt="Joey in Vale, Colorado."
          className={styles.avatar}
        />
        <div className={styles.onlineStatus} />
      </div>
      <div className={styles.clearFix} />
      <h1>{name}</h1>
      <p>{bio}</p>
      <p>Joey's interests:</p>
      <ul>
        <li>Coding</li>
        <li>Music</li>
        <li>Baking</li>
      </ul>
    </div>
  );
}

We can now do this with all of our other data:

// Profile.js
import styles from "./Profile.module.css";

export default function Profile({ name, bio, online, avatar, interests }) {
  return (
    <div className={styles.profile}>
      <div className={styles.avatarWrapper}>
        <img src={avatar.src} alt={avatar.alt} className={styles.avatar} />
        <div className={styles.onlineStatus} />
      </div>
      <div className={styles.clearFix} />
      <h1>{name}</h1>
      <p>{bio}</p>
      <p>{name}'s interests:</p>
      <ul>
        {interests.map((interest, i) => (
          <li key={`interest${i}`}>{interest}</li>
        ))}
      </ul>
    </div>
  );
}

All of this should be pretty straightforward by now. Check out React: Iteration if you're curious what the <li key={interest${i}}> stuff is all about.

The last thing I want to do is some conditional styling on the circular <div> around the avatar image. If online is true, I want that circle to be green, and if online is false, I want it to be red. Thankfully, we already have some .active and inactive CSS classes with the styles we want to apply:

/* in Profile.module.css: */
.active {
  background-color: green;
}

.inactive {
  background-color: red;
}

Now we just need to add the correct class depending on the value of the online prop:

// in Profile.js
<div
  className={`${styles.onlineStatus} ${
    online ? styles.active : styles.inactive
  }`}
/>

Again, if this syntax makes you scratch your head, I encourage you to check out React: Styling.

Default Values

You might be asking yourself, "What happens if I forget to pass a necessary prop?" That's a great question, and the answer is that it depends. In some cases, an element that uses a missing prop will simply not render, but not throw an error. This would be considered silently failing. In other cases, an error will get thrown. If you're working in a React-based static site generator like Next.js or Gatsby, the static build step might fail.

One of the ways that you can guard against this issue is to define default values to fall back on if a prop is not given value when the component is called. You can do so using JavaScript's Default Parameters feature. Here's what this might look like in our running Profile example:

// Profile.js
import styles from "./Profile.module.css";

export default function Profile({
  name = "Name",
  bio = "Bio",
  online = false,
  avatar = {
    src: "https://i.imgur.com/YfeOqp2s.jpg",
    alt: "Stock headshot",
  },
  interests = [],
}) {
  return (
    <div className={styles.profile}>
      <div className={styles.avatarWrapper}>
        <img src={avatar.src} alt={avatar.alt} className={styles.avatar} />
        <div
          className={`${styles.onlineStatus} ${
            online ? styles.active : styles.inactive
          }`}
        />
      </div>
      <div className={styles.clearFix} />
      <h1>{name}</h1>
      <p>{bio}</p>
      <p>{name}'s interests:</p>
      <ul>
        {interests.map((interest, i) => (
          <li key={`interest${i}`}>{interest}</li>
        ))}
      </ul>
    </div>
  );
}

You might see Default Props defined within a separate object in legacy projects. This might look something like:

class Button extends Component {
  static defaultProps = {
    color: 'blue'
  };

  render() {
    return <button className={this.props.color}>click me</button>;
  }
}

This is the old way of doing things. Per this StackOverflow thread, there are implications for using static default props when composing non-functional React components (i.e., legacy Class components). See more in the Legacy API sections of the React docs.

Prop Types

Another issue to look out for is unintended type coercion with your props. I've written extensively about type coercion in JavaScript in this blog (see: Coercion: ToString, ToNumber, and ToBoolean, Explicit Coercion, Implicit Coercion, and == and ===). Type coercion can also cause issues with props. For example, if a prop is supposed to be a string, but is delivered as a number or boolean, issues may arise.

One way to safeguard against this is to use the prop-types library. To start out, you'll need to install the prop-types NPM Package, and import PropTypes at the top of the component where you want to use it. You then add a .PropTypes property to the functional component, and specify the value type for each prop. Here's what that might look like:

// Profile.js
import PropTypes from "prop-types";
import styles from "./Profile.module.css";

export default function Profile({
  name = "Name",
  bio = "Bio",
  online = false,
  avatar = {
    src: "https://i.imgur.com/YfeOqp2s.jpg",
    alt: "Stock headshot",
  },
  interests = [],
}) {
  return (
    <div className={styles.profile}>
      <div className={styles.avatarWrapper}>
        <img src={avatar.src} alt={avatar.alt} className={styles.avatar} />
        <div
          className={`${styles.onlineStatus} ${
            online ? styles.active : styles.inactive
          }`}
        />
      </div>
      <div className={styles.clearFix} />
      <h1>{name}</h1>
      <p>{bio}</p>
      <p>{name}'s interests:</p>
      <ul>
        {interests.map((interest, i) => (
          <li key={`interest${i}`}>{interest}</li>
        ))}
      </ul>
    </div>
  );
}

Profile.propTypes = {
  name: PropTypes.string,
  bio: PropTypes.string,
  online: PropTypes.bool,
  avatar: PropTypes.object,
  interests: PropTypes.array,
};

Now if you pass an invalid value type for a prop, it will warn you. For example, if I pass the number 12345 for name when name expects a string, the following Warning appears in the console:

// in App.js:
<Profile name={12345} />
// Expected result: Warning: Failed prop type: Invalid prop `name` of type `number` supplied to `Profile`, expected `string`.

It's worth noting that, like static default props, prop types are an older way of doing things. More modern ways of doing type checking include using TypeScript or JSDoc.

The children Prop

There is a special reserved prop named children for each React Component. You can read about it more in the children Prop section of my React: Basics blog post.

Changing Props Over Time

Over the lifecycle of a program, props are going to change. In our example, a user may log on and then log off, and their online status will change that green div's color to be red. This is done with State. I will be covering State in React more extensively in the future of this blog. Props themselves are immutable, which is to say that they don't change directly. Instead, if the application's state changes, and that state affects the component that's rendered, that state will pass new props to the component, triggering a re-render and throwing out the old props. Again, more on that to come. State is a big topic!

Review

  • Props are pieces of information that you pass to a JSX tag, and are most commonly seen as the data passed to React components. This pattern makes those components as flexible and reusable as possible
  • When you call a React component, you pass it each prop with that prop's value in a manner that looks very similar to HTML attributes
  • Within that React component's file, you can access those props from the props object, or destructure them using the property names and embed those prop names into the markup of the component
  • Props can be any valid JavaScript value
  • You can guard against errors that arise from missing props using default prop values
  • You can guard against errors that arise from improperly typed props using prop types, TypeScript, or JSDoc
  • There is a special reserved prop named children for each React Component
  • Props are immutable. If your application's state changes, and that state affects the component that's rendered, that state will pass new props to the component, triggering a re-render and throwing out the old props