React (ui.dev) - pt 3

Introduction to Props

Whenever you have a system that is reliant upon composition, it’s critical that each piece of that system has an interface for accepting data from outside of itself. You can see this clearly illustrated by looking at something you’re already familiar with, functions.

function getProfilePic (username) {
  return 'https://photo.fb.com/' + username
}

function getProfileLink (username) {
  return 'https://www.fb.com/' + username
}

function getAvatarInfo (username) {
  return {
    pic: getProfilePic(username),
    link: getProfileLink(username)
  }
}

getAvatarInfo('tylermcginnis')

We’ve seen this code before as our very soft introduction to function composition. Without the ability to pass data, in this case username , to each of our of functions, our composition would break down.

Similarly, because React relies heavily on composition, there needs to exist a way to pass data into components. This brings us to our next important React concept, props .

Props are to components what arguments are to functions.

Again, the same intuition you have about functions and passing arguments to functions can be directly applied to components and passing props to components.

There are two parts to understanding how props work. First is how to pass data into components, and second is accessing the data once it’s been passed in.

Passing data to a component

This one should feel natural because you’ve been doing something similar ever since you learned HTML. You pass data to a React component the same way you’d set an attribute on an HTML element.

![](upload://s3EAGSlgulCWUrkvoILhbeKuTTL.png)

<Hello name='Tyler' />

In the example above, we’re passing in a name prop to the Hello component.

Accessing props

Now the next question is, how do you access the props that are being passed to a component? In a class component, you can get access to props from the props key on the component’s instance ( this ).

class Hello extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    )
  }
}

Each prop that is passed to a component is added as a key on this.props . If no props are passed to a component, this.props will be an empty object.

class Hello extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.first} {this.props.last}</h1>
    )
  }
}

<Hello first='Tyler' last='McGinnis' />

It’s important to note that we’re not limited to what we can pass as props to components. Just like we can pass functions as arguments to other functions, we’re also able to pass components (or really anything we want) as props to other components.

<Profile 
  username='tylermcginnis'
  authed={true}
  logout={() => handleLogout()}
  header={<h1>👋</h1>}
/>

If you pass a prop without a value, that value will be set to true . These are equivalent.

<Profile authed={true} />

<Profile authed />

(Practice) Props

Below are links to three unfinished, mini React apps. Your job, should you choose to accept it, is for each app, analyze how the props are being passed to the Badge component and then finish implementing it. Your final UI for each one should look like this.

solution

Practice Problems

(Solution) Props

Solutions

Rendering Lists in React

Eventually you come to accept that as an app developer, your primary job is to render lists. It’s so fundamental, that most frameworks come with a special API to accomplish it.

Vue
<ul id="tweets">
  <li v-for="tweet in tweets">
    {{ tweet.text }}
  </li>
</ul>
Angular
<ul id="tweets">
  <li *ngFor="let tweet of tweets">
    {{ tweet.text }}
  </li>
</ul>

React

React takes a different approach. When possible, React tries to keep the API surface to a minimum. To do that, it relies heavily on features that JavaScript provides out of the box. When rendering a list, the end goal, as seen in the Vue and Angular examples above, is to create a list of <li> elements that we can then show to the UI. Can you think of anything built into JavaScript itself that would help us accomplish this? What about .map ? Typically you use .map when you need to create a new array, based on a previous array. Something like this.

const tweets = [
  { id: 1, stars: 13, text: 'Turns out "git reset --hard HEAD^" was a terrible idea.' },
  { id: 2, stars: 87, text: 'Tech conferences are too expensive.' },
  { id: 3, stars: 51, text: 'Clean code is subjective. Optimize for deletion.' },
  { id: 4, stars: 19, text: 'Maybe the real benefit of open source was the friendships we made along the way?' },
]

const stars = tweets.map((tweet) => tweet.stars) // [13,87,51,19]

Except now instead of creating a new array of stars , we want to create a new array of <li> s. Using the same .map method, let’s throw in some JSX.

<ul id="tweets">
  {tweets.map((tweet) => (
    <li>
      {tweet.text}
    </li>
  ))}
</ul>

:ok_hand: Solid. There’s no new API you need to remember. If you can remember how .map works, you can remember how to create a list in React.

Caveat :poop:

Unfortunately, we’re not quite done yet. There’s just one small addition we need to make to our code. Whenever you use .map to create a list in React, you need to make sure that you add a unique key prop to each list item.

<ul id="tweets">
  {tweets.map((tweet) => (
    <li key={tweet.id}>
      {tweet.text}
    </li>
  ))}
</ul>

It’s React’s job to make rendering the list as fast as possible. When you give each list item a unique key prop, it helps React know which items, if any, change throughout different renders of that component.

(Practice) Rendering Lists

Below are links to two unfinished, mini React apps. Your job, should you choose to accept it, is for each app, create a list of <li> elements from the friends array being passed into List . Your final UI for each one should look like this.

solution

Practice Problems

(Solution) Rendering Lists

Solutions

(Project) Popular Navbar

The code for this video can be found here.

The commit for this video can be found here.

Understanding the “this” keyword in JavaScript

This is originally part of our Advanced JavaScript course. However, it’s applicable to us here as well.

Before diving into the specifics of the this keyword in JavaScript, it’s important to take a step back and first look at why the this keyword exists in the first place. The this keyword allows you to reuse functions with different contexts. Said differently, the “this” keyword allows you to decide which object should be focal when invoking a function or a method. Everything we talk about after this will build upon that idea. We want to be able to reuse functions or methods in different contexts or with different objects.

The first thing we’ll look at is how to tell what the this keyword is referencing. The first and most important question you need to ask yourself when you’re trying to answer this question is “ Where is this function being invoked? ”. The only way you can tell what the this keyword is referencing is by looking at where the function using the this keyword was invoked.

To demonstrate this with an example you’re already familiar with, say we had a greet function that took in a name an alerted a welcome message.

function greet (name) {
  alert(`Hello, my name is ${name}`)
}

If I were to ask you exactly what greet was going to alert, what would your answer be? Given only the function definition, it’s impossible to know. In order to know what name is, you’d have to look at the function invocation of greet .

greet('Tyler')

It’s the exact same idea with figuring out what the this keyword is referencing. You can even think about the this keyword as you would a normal argument to a function - it’s going to change based on how the function is invoked.

Now that you know the first step to figuring out what the this keyword is referencing is to look at where the function is being invoked, what’s next? To help us with the next step, we’re going to establish 5 rules or guidelines.

  1. Implicit Binding
  2. Explicit Binding
  3. new Binding
  4. Lexical Binding
  5. window Binding

Implicit Binding

Remember, the goal here is to be able to look at a function definition using the this keyword and tell what this is referencing. The first and most common rule for doing that is called the Implicit Binding . I’d say it’ll tell you what the this keyword is referencing about 80% of the time.

Let’s say we had an object that looked like this

const user = {
  name: 'Tyler',
  age: 27,
  greet() {
    alert(`Hello, my name is ${this.name}`)
  }
}

Now, if you were to invoke the greet method on the user object, you’d do so be using dot notation.

user.greet()

This brings us to the main key point of the implicit binding rule. In order to figure out what the this keyword is referencing, first, look to the left of the dot when the function is invoked . If there is a “dot”, look to the left of that dot to find the object that the this keyword is referencing.

In the example above, user is to “the left of the dot” which means the this keyword is referencing the user object. So, it’s as if , inside the greet method, the JavaScript interpreter changes this to user .

greet() {
  // alert(`Hello, my name is ${this.name}`)
  alert(`Hello, my name is ${user.name}`) // Tyler
}

Let’s take a look at a similar, but a slightly more advanced example. Now, instead of just having a name , age , and greet property, let’s also give our user object a mother property which also has a name and greet property.

const user = {
  name: 'Tyler',
  age: 27,
  greet() {
    alert(`Hello, my name is ${this.name}`)
  },
  mother: {
    name: 'Stacey',
    greet() {
      alert(`Hello, my name is ${this.name}`)
    }
  }
}

Now the question becomes, what is each invocation below going to alert?

user.greet()
user.mother.greet()

Whenever we’re trying to figure out what the this keyword is referencing we need to look to the invocation and see what’s to the “left of the dot”. In the first invocation, user is to the left of the dot which means this is going to reference user . In the second invocation, mother is to the left of the dot which means this is going to reference mother .

user.greet() // Tyler
user.mother.greet() // Stacey

As mentioned earlier, about 80% of the time there will be an object to the “left of the dot”. That’s why the first step you should take when figuring out what the this keyword is referencing is to “look to the left of the dot”. But, what if there is no dot? This brings us to our next rule -

Explicit Binding

Now, what if instead of our greet function being a method on the user object, it was just its own standalone function.

function greet () {
  alert(`Hello, my name is ${this.name}`)
}

const user = {
  name: 'Tyler',
  age: 27,
}

We know that in order to tell what the this keyword is referencing we first have to look at where the function is being invoked. Now, this brings up the question, how can we invoke greet but have it be invoked with the this keyword referencing the user object. We can’t just do user.greet() like we did before because user doesn’t have a greet method. In JavaScript, every function contains a method which allows you to do exactly this and that method is named call .

“call” is a method on every function that allows you to invoke the function specifying in what context the function will be invoked.

With that in mind, we can invoke greet in the context of user with the following code -

greet.call(user)

Again, call is a property on every function and the first argument you pass to it will be the context (or the focal object) in which the function is invoked. In other words, the first argument you pass to call will be what the this keyword inside that function is referencing.

This is the foundation of rule #2 (Explicit Binding) because we’re explicitly (using .call ), specifying what the this keyword is referencing.

Now let’s modify our greet function just a little bit. What if we also wanted to pass in some arguments? Say along with their name, we also wanted to alert what languages they know. Something like this

function greet (l1, l2, l3) {
  alert(
    `Hello, my name is ${this.name} and I know ${l1}, ${l2}, and ${l3}`
  )
}

Now to pass arguments to a function being invoked with .call , you pass them in one by one after you specify the first argument which is the context.

function greet (l1, l2, l3) {
  alert(
    `Hello, my name is ${this.name} and I know ${l1}, ${l2}, and ${l3}`
  )
}

const user = {
  name: 'Tyler',
  age: 27,
}

const languages = ['JavaScript', 'Ruby', 'Python']

greet.call(user, languages[0], languages[1], languages[2])

This works and it shows how you can pass arguments to a function being invoked with .call . However, as you may have noticed, it’s a tad annoying to have to pass in the arguments one by one from our languages array. It would be nice if we could just pass in the whole array as the second argument and JavaScript would spread those out for us. Well good news for us, this is exactly what .apply does. .apply is the exact same thing as .call , but instead of passing in arguments one by one, you can pass in a single array and it will spread each element in the array out for you as arguments to the function.

So now using .apply , our code can change into this (below) with everything else staying the same.

const languages = ['JavaScript', 'Ruby', 'Python']

// greet.call(user, languages[0], languages[1], languages[2])
greet.apply(user, languages)

So far under our “Explicit Binding” rule we’ve learned about .call as well as .apply which both allow you to invoke a function, specifying what the this keyword is going to be referencing inside of that function. The last part of this rule is .bind . .bind is the exact same as .call but instead of immediately invoking the function, it’ll return a new function that you can invoke at a later time. So if we look at our code from earlier, using .bind , it’ll look like this

function greet (l1, l2, l3) {
  alert(
    `Hello, my name is ${this.name} and I know ${l1}, ${l2}, and ${l3}`
  )
}

const user = {
  name: 'Tyler',
  age: 27,
}

const languages = ['JavaScript', 'Ruby', 'Python']

const newFn = greet.bind(user, languages[0], languages[1], languages[2])
newFn() // alerts "Hello, my name is Tyler and I know JavaScript, Ruby, and Python"

new Binding

The third rule for figuring out what the this keyword is referencing is called the new binding. If you’re unfamiliar with the new keyword in JavaScript, whenever you invoke a function with the new keyword, under the hood, the JavaScript interpreter will create a brand new object for you and call it this . So, naturally, if a function was called with new , the this keyword is referencing that new object that the interpreter created.

function User (name, age) {
  /*
    Under the hood, JavaScript creates a new object
    called `this` which delegates to the User's prototype
    on failed lookups. If a function is called with the
    new keyword, then it's this new object that interpreter
    created that the this keyword is referencing.
  */

  this.name = name
  this.age = age
}

const me = new User('Tyler', 27)

Lexical Binding

At this point, we’re on our 4th rule and you may be feeling a bit overwhelmed. That’s fair. The this keyword in JavaScript is arguably more complex than it should be. Here’s the good news, this next rule is the most intuitive.

Odds are you’ve heard of and used an arrow function before. They’re new as of ES6. They allow you to write functions in a more concise format.

friends.map((friend) => friend.name)

Even more than conciseness, arrow functions have a much more intuitive approach when it comes to this keyword. Unlike normal functions, arrow functions don’t have their own this . Instead, this is determined lexically . That’s a fancy way of saying this is determined how you’d expect, following the normal variable lookup rules. Let’s continue with the example we used earlier. Now, instead of having languages and greet as separate from the object, let’s combine them.

const user = {
  name: 'Tyler',
  age: 27,
  languages: ['JavaScript', 'Ruby', 'Python'],
  greet() {}
}

Earlier we assumed that the languages array would always have a length of 3. By doing so we were able to use hardcoded variables like l1 , l2 , and l3 . Let’s make greet a little more intelligent now and assume that languages can be of any length. To do this, we’ll use .reduce in order to create our string.

const user = {
  name: 'Tyler',
  age: 27,
  languages: ['JavaScript', 'Ruby', 'Python'],
  greet() {
    const hello = `Hello, my name is ${this.name} and I know`

    const langs = this.languages.reduce(function (str, lang, i) {
      if (i === this.languages.length - 1) {
        return `${str} and ${lang}.`
      }

      return `${str} ${lang},`
    }, "")

    alert(hello + langs)
  }
}

That’s a lot more code but the end result should be the same. When we invoke user.greet() , we expect to see Hello, my name is Tyler and I know JavaScript, Ruby, and Python. . Sadly, there’s an error. Can you spot it? Grab the code above and run it in your console. You’ll notice it’s throwing the error Uncaught TypeError: Cannot read property 'length' of undefined . Gross. The only place we’re using .length is on line 9, so we know our error is there.

if (i === this.languages.length - 1) {}

According to our error, this.languages is undefined. Let’s walk through our steps to figure out what that this keyword is referencing cause clearly, it’s not referencing user as it should be. First, we need to look at where the function is being invoked. Wait? Where is the function being invoked? The function is being passed to .reduce so we have no idea. We never actually see the invocation of our anonymous function since JavaScript does that itself in the implementation of .reduce . That’s the problem. We need to specify that we want the anonymous function we pass to .reduce to be invoked in the context of user . That way this.languages will reference user.languages . As we learned above, we can use .bind .

const user = {
  name: 'Tyler',
  age: 27,
  languages: ['JavaScript', 'Ruby', 'Python'],
  greet() {
    const hello = `Hello, my name is ${this.name} and I know`

    const langs = this.languages.reduce(function (str, lang, i) {
      if (i === this.languages.length - 1) {
        return `${str} and ${lang}.`
      }

      return `${str} ${lang},`
    }.bind(this), "")

    alert(hello + langs)
  }
}

So we’ve seen how .bind solves the issue, but what does this have to do with arrow functions. Earlier I said that with arrow functions " this is determined lexically . That’s a fancy way of saying this is determined how you’d expect, following the normal variable lookup rules."

In the code above, following just your natural intuition, what would the this keyword reference inside of the anonymous function? For me, it should reference user . There’s no reason to create a new context just because I had to pass a new function to .reduce . And with that intuition comes the often overlooked value of arrow functions. If we re-write the code above and do nothing but use an anonymous arrow function instead of an anonymous function declaration, everything “just works”.

const user = {
  name: 'Tyler',
  age: 27,
  languages: ['JavaScript', 'Ruby', 'Python'],
  greet() {
    const hello = `Hello, my name is ${this.name} and I know`

    const langs = this.languages.reduce((str, lang, i) => {
      if (i === this.languages.length - 1) {
        return `${str} and ${lang}.`
      }

      return `${str} ${lang},`
    }, "")

    alert(hello + langs)
  }
}

Again the reason for this because with arrow functions, this is determined “lexically”. Arrow functions don’t have their own this . Instead, just like with variable lookups, the JavaScript interpreter will look to the enclosing (parent) scope to determine what this is referencing.

window Binding

Finally is the “catch-all” case - the window binding. Let’s say we had the following code

function sayAge () {
  console.log(`My age is ${this.age}`)
}

const user = {
  name: 'Tyler',
  age: 27
}

As we covered earlier, if you wanted to invoke sayAge in the context of user , you could use .call , .apply , or .bind . What would happen if we didn’t use any of those and instead just invoked sayAge as you normally would

sayAge() // My age is undefined

What you’d get is, unsurprisingly, My age is undefined because this.age would be undefined. Here’s where things get a little weird. What’s really happening here is because there’s nothing to the left of the dot, we’re not using .call , .apply , .bind , or the new keyword, JavaScript is defaulting this to reference the window object. What that means is if we add an age property to the window object, then when we invoke our sayAge function again, this.age will no longer be undefined but instead, it’ll be whatever the age property is on the window object. Don’t believe me? Run this code,

window.age = 27

function sayAge () {
  console.log(`My age is ${this.age}`)
}

Pretty gnarly, right? That’s why the 5th rule is the window Binding . If none of the other rules are met, then JavaScript will default the this keyword to reference the window object.

As of ES5, if you have “strict mode” enabled, JavaScript will do the right thing and instead of defaulting to the window object will just keep “this” as undefined.

'use strict'

window.age = 27

function sayAge () {
  console.log(`My age is ${this.age}`)
}

sayAge() // TypeError: Cannot read property 'age' of undefined

So putting all of our rules into practice, whenever I see the this keyword inside of a function, these are the steps I take in order to figure out what it’s referencing.

  1. Look to where the function was invoked.
  2. Is there an object to the left of the dot? If so, that’s what the “this” keyword is referencing. If not, continue to #3.
  3. Was the function invoked with “call”, “apply”, or “bind”? If so, it’ll explicitly state what the “this” keyword is referencing. If not, continue to #4.
  4. Was the function invoked using the “new” keyword? If so, the “this” keyword is referencing the newly created object that was made by the JavaScript interpreter. If not, continue to #5.
  5. Is “this” inside of an arrow function? If so, its reference may be found lexically in the enclosing (parent) scope. If not, continue to #6.
  6. Are you in “strict mode”? If yes, the “this” keyword is undefined. If not, continue to #7.
  7. JavaScript is weird. “this” is referencing the “window” object.

Managing State in React

There are a lot of advantages to using React. In my opinion, one of the biggest has to do with the ability of components to manage their own state. Even just the simple mental model alone has enormous benefits. With React, you don’t need to keep the entire application state in your head. Instead, the surface layer of your concerns can be minimized to the state of an individual component.

In this post, there are two questions we’re going to answer. First, how do you add state to a React component? Second, how do you update a React component’s state?

Adding State

To add state to a class component, you’ll use the constructor method. constructor is part of the ES6 class spec and isn’t a React specific method.

If you’re new to ES6 classes, check out this post

class Hello extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      name: 'Tyler'
    }
  }
  render() {
    return (
      <h1>Hello, {this.state.name}</h1>
    )
  }
}

“Just JavaScript”. There are a few things to note here. First, get in the habit of calling super(props) in the constructor. super refers to the constructor method of the class you’re extending, in this case, React.Component . Again, this is just a JavaScript thing. You can’t use this in a constructor until after you’ve called super . There are also reasons you have to pass props to super that are superfluous to this post.

Next, you add state to your class component by adding a state property on the component’s instance, this . By adding state to the instance, you can now access it (via this.state ) anywhere in your class.

Updating State

Now that you know how to add state to your component, the next question becomes how do you update that state?

Your first intuition might be to update the state directly.

this.state.name = 'Mikenzi'

That’s not a good idea. We’ve talked a few times how in React, your View is a function of your State. You don’t need to worry about updating the DOM because React will do that for you whenever the state of your component changes. If you update the state directly yourself, React will have no idea that the component’s state changed and therefore won’t be able to update the UI.

Instead, React gives you a helper method you can use to update the state of your component (and re-render the UI). It’s called setState and it lives on the component’s instance, this . There are two forms of setState . The first, and most popular, accepts an object as its first argument that is merged with the current state.

updateName(newName) {
  this.setState({ 
    name: newName 
  })
}

When the updateName method is invoked, React will update the name property on the component’s state to be whatever newName is. Then, because the state changed, React will re-invoke the render method and get a new description of the UI based on the new state. Finally, with that new description of the UI, React will update the DOM.

Here’s a full version.

class Hello extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      name: 'Tyler'
    }

    this.updateName = this.updateName.bind(this)
  }
  updateName() {
    this.setState({
      name: 'Mikenzi'
    })
  }
  render() {
    return (
      <React.Fragment>
        <h1>Hello, {this.state.name}</h1>
        <button onClick={this.updateName}>Change Name</button>
      </React.Fragment>
    )
  }
}

The biggest “gotcha” when dealing with updating state has to do with the this keyword. Notice that we had to include the .bind line in our constructor.

this.updateName = this.updateName.bind(this)

Why is that? Well, without that, when a user clicks on the button , they’ll get an error of

TypeError: Cannot read property ‘setState’ of undefined

When figuring out what the this keyword is referencing, you first need to look at where the function using the this keyword is invoked. In our example, we have no idea where updateName is being invoked because we’re passing it as a prop to onClick . That means the React internals get to decide how and in which context it’s invoked. To remedy this, we use .bind inside of the constructor to say “whenever updateName is invoked, always make sure it’s invoked in the context of the current component.”

Note that when you call setState passing in an object, that object will be merged with the current state, it won’t replace it. That means if you have other properties on your state that you aren’t updating, they’ll remain the same.

Updating State: The Other Way

Earlier I mentioned that there are two forms of setState . The first, and most popular is passing an object to setState as you just saw. The second form of setState accepts a function as its first argument instead of an object. That function is then passed the current state and the object it returns will be merged into the new state.

addFriend(newFriend) {
  this.setState((state) => {
    return {
      friends: state.friends.concat(newFriend)
    }
  })
}

In the example above, addFriend is responsible for taking in a newFriend and adding it to state.friends .

Can you spot when you’d want to use the function setState over the object setState ? The key is it depends on what changes you’re making to the state. __If you’re updating the current state based on the previous state (i.e., adding newFriend to the end of an existing friends array), use the function setState . For everything else, use the object setState .

The reason for this is because state updates may be asynchronous. There’s a lot of work happening under the hood when you call setState , so for React to guarantee that the state value is what you’d expect it to be, they have you pass them a function that receives state rather than relying on referencing state from the component instance.

(Practice) State

Below are links to two unfinished, mini React apps. Each of them have methods that you need to implement.

The first app allows the user to toggle between “light” mode and “dark” mode.

The second app is a counter app. When you hit the + button, the count should increase by 1. When you hit the - button, it should decrease by 1.

Practice Problems

(Solution) State

Solutions

(Project) Navbar State

The code for this video can be found here.

The commit for this video can be found here.

Functional Components

If you’re using React correctly, you’re going to have a lot of components that take in some data via props and output some UI - that is, components with just a render method.

class HelloWorld extends React.Component {
  render () {
    return (
      <div>Hello {this.props.name}</div>
    )
  }
}

This may seem like a lot of overhead for creating such a simple component, because it is. There have been a few times now where we’ve mentioned how the same intuition you have about functions and function composition can be directly applied to creating React components with component composition. The disconnect, however, is that to build a component we’ve been using ES6 classes. What if, instead, we could use a regular ol’ function? It turns out you can, but there’s one caveat, that component needs to have only a render method. Again, if all your class component does is (optionally) take in some props and render some UI, you can make it a functional component instead.

function HelloWorld (props) {
  return (
    <div>Hello {props.name}</div>
  )
}

:heart: Much better. Now instead of having to worry about the this keyword, your component is passed props as the first argument to the function. This is a lot cleaner and makes creating React components more natural since you’re literally just making a function.

Recently React introduced Hooks, an addition to React which allows you to create functional components that can create and manage their own state. Because of this, Hooks drastically reduce the need for creating class components. However, as mentioned before, we won’t use Hooks in this course. Instead, they’ll have their own course fully dedicated to them that you should take once you’ve completed this course.

(Project) Languages Nav

The code for this video can be found here.

The commit for this video can be found here.

PropTypes

JavaScript has seven data types, Boolean , Null , Undefined , Number , String , Symbol , and Object . A deceivingly large amount of bugs can be caught and prevented by validating the different data types that are used and passed around your application. This isn’t a new idea; entire programming languages have been built around the ability to type check. In recent years, tools like Flow and TypeScript have even made their way into the JavaScript ecosystem. Though these tools are powerful, they’re a significant commitment and often require a lot of overhead. Often times, when building a React app, validating the props that are being passed to a component is all you need. If you can be sure that the props being passed to a component are the right type, for the most part, you can assume your component is going to run correctly. More than that, if you can be sure that the props being passed to a component aren’t the right type, you can assume your component isn’t going to run correctly.

The package we’ll use to declare types for our props is, naturally, called prop-types .

Here’s the big picture. Whenever you create a component that accepts props, you’ll add a static propTypes property to that component. propTypes will be an object whose keys represent the props the component accepts and whose values represent the data types for those props. During development, if a prop being passed to a component doesn’t match the data type specified in propTypes , a warning will be shown in the console.

Let’s start with a simple Hello component that takes in one prop, name which is a string and is required.

import React from 'react'
import PropTypes from 'prop-types'

export default function Hello ({ name }) {
  return <h1>Hello, {name}</h1>
}

Hello.propTypes = {
  name: PropTypes.string.isRequired
}

A few things to note here. First, the naming conventions. PropTypes (capital P ) is what we call the object being exported from the prop-types package. propTypes (lower case p ) is the name of the static property we add to our component. Second, is how we use the PropTypes object to declare the type of prop. In our example, we’re saying that anytime you use the Hello component, it’s required that you pass to it a name prop which is a string. If name is not included as a prop or it’s not of type string , a warning will be shown in the console.

<Hello name='Tyler' /> // 👍

<Hello /> 
// Warning: Failed prop type: The prop `name` is marked as required in `Hello`, but its value is `undefined`.

<Hello name={true}/> 
// Warning: Failed prop type: Invalid prop `name` of type `boolean` supplied to `Hello`, expected `string`.

Class components follow the same syntax. You add propTypes as a static property on the class itself.

class Hello extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

Hello.propTypes = {
  name: PropTypes.string.isRequired
}

Now that you understand how to validate strings, let’s look at the rest of the prop-types API. For the most part, it’s straight forward. There are a few gotchas though. To do this, we can look at every property on the PropTypes object.

Note that by default, every prop is optional. To make them required, append .isRequired as we did above.

You don’t need to memorize all of these. In fact, I probably wouldn’t even read this whole thing. Skim it then use it more as a reference guide if you need it. The biggest gotchas are func and bool so check those out.

PropTypes.any

The prop being passed into the component can be of any data type. Use this sparingly as it neglects all the benefits of using PropTypes.

PropTypes.array

The prop being passed into the component must be an array.

PropTypes.arrayOf:

The prop being passed into the component must be an array of a certain type. For example, if you had a prop that needed to be an array of strings, you’d use PropTypes.arrayOf(PropTypes.string) .

...

List.propTypes = {
  friends: PropTypes.arrayOf(PropTypes.string)
}

<List friends={['Mikenzi', 'Cash', 'Jake']}/>

PropTypes.bool

The prop being passed into the component must be a boolean.

PropTypes.element

The prop being passed into the component must be a React element.

...

Dashboard.propTypes = {
  header: PropTypes.element
}

<Dashboard header={<Navbar />} />

PropTypes.exact

The prop being passed into the component must be an object with a specific shape. Any extra properties will throw an error.

...

Header.propTypes = {
  user: PropTypes.exact({
    name: PropTypes.string,
    age: PropTypes.number,
    submit: PropTypes.func,
  })
}

<Header
  user={{
    name: 'Tyler',
    age: 28,
    submit: () => ({})
  }}
/>

PropTypes.func

The prop being passed into the component must be a function.

PropTypes.instanceOf

The prop being passed into the component must be an instance of a certain class.

class User {
  ...
}

Header.propTypes = {
  user: PropTypes.instanceOf(User)
}

const tyler = new User('tyler')
<Header user={tyler} />

PropTypes.number

The prop being passed into the component must be a number.

PropTypes.object

The prop being passed into the component must be an object.

PropTypes.objectOf

The prop being passed into the component must be an object whose values are all of a certain type.

...

List.propTypes = {
  scores: PropTypes.objectOf(PropTypes.number)
}

<List scores={{
  jake: 9,
  tyler: 5,
  mikenzi: 10
}} />

PropTypes.oneOf

The prop being passed into the component must be one of a certain value.

...

List.propTypes = {
  order: PropTypes.oneOf(['ascending', 'descending'])
  items: PropTypes.array,
}

<List items={users} order='ascending' />

PropTypes.oneOfType

The prop being passed into the component must be one of a certain type.

...

Post.propTypes = {
  date: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.instanceOf(Date)
  ])
}

<Post date={new Date()} />

PropTypes.shape

The prop being passed into the component must have a certain shape. It’s similar to PropTypes.exact but, unlike exact , it allows you to include extra properties.

...

Header.propTypes = {
  user: PropTypes.shape({
    name: PropTypes.string,
    age: PropTypes.number,
    submit: PropTypes.func,
  })
}

<Header
  user={{
    name: 'Tyler',
    age: 28,
    submit: () => ({}),
    authed: true
  }}
/>

PropTypes.string

The prop being passed into the component must be a string.

PropTypes.symbol

The prop being passed into the component must be a symbol.