What a Flerken are React PropTypes and How to Use Them in a Fancy Way?

One of the main concepts of a React Component is its props. The props are used to pass data to a component, just like a parameter is to a function, with the difference that when the data changes, the component that receives the prop re-renders automatically. They can become a problem when you have a large project with a lot of components because you will probably forget the type of each prop, and when someone else needs to change or use this code they will definitely be lost. One solution for that is to use the components propTypes property.

What are propTypes?

The propTypes is an attribute of React components that allows validating the types of the component props (pretty intuitive hm?). So you may ask “but why do we need these types validations? I’m doing JavaScript, I don’t need to specify types everywhere”. Yes, I agree with you, I think that we don’t need to specify our prop types to make the application run, but that’s not the point of propTypes checking, it will not stop your software and break it if the type of your prop is wrong, but it will warn you about that. So let’s just say that you have a component called Todo and it has a prop id that is a string and you are already declaring this prop type on your component. If you send a number through your id prop, you’ll see this warning in your browser console:

And that’s the real point of it, this way you can enforce your application integrity, and you will prevent things like doing a map in what you thought was an array and it’s actually an object, and of course, run away from the scary undefined. Also, the type checking will just run in the development and in the test environments.

How do we use it?

So, now that we have an idea of what this beautiful thing is, let’s take a look at how to use it. The first thing that you will need to do is install prop-types package, if you are using create-react-app to bundle your application it won’t be necessary, otherwise, you can install it like this:

npm install --save-dev prop-types

or

yarn add prop-types --dev

To use it you will need to import PropTypes from the prop-types package. Then you will need to create an object with your component props and its respective types using PropTypes and assign it to your component propTypes attribute (Notice that the attribute is propTypes and the constant is PropTypes).

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

const User = ({ name, age }) =>
  <div>
    My name is: {name}
    I am {age} years old
  </div>

User.propTypes = {
  name: PropTypes.string,
  age: PropTypes.number
}

export default User

So in this example, we have a component User that receives two different props, name (a string) and age (a number), so if these props are passed with different types it will log a warning.

PropTypes package provides us a lot of base types, like the ones we used before (string and number) and others like array, object and so on, all of them can be found on the React prop-types docs. Also, you can define a default value to your prop, like this:

import PropTypes from 'prop-types'

...

User.propTypes = {
  name: PropTypes.string,
  age: PropTypes.number
}

User.defaultProps = {
  name: 'Unknown'
}

export default User

So if you don’t pass any value to your name prop, it will fall back to ‘Unknown’.

And… How can we use this more effectively?

The package PropTypes gives us a type checking called shape that can be used to validate all attributes from an object. To do this validation it receives an object with all attributes as param, like this:

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

const User = ({ user }) =>
  <div>
    My name is: {user.name}
    I am {user.age} years old
  </div>

User.propTypes = {
  user: PropTypes.shape({
    name: PropTypes.string.isRequired,
    age: PropTypes.number.isRequired
  })
}

export default User

Notice that now name and age are inside an object called user and its propTypes are being checked inside the PropTypes.shape.

Now consider that you probably need these same user attributes in several components, consequently having to write this type checking for each one of them. To prevent this we can write this shape somewhere else and then just reuse it.

So let’s create a model file for the user, that will keep all business rules about it, including its shape, and let’s reuse it on other components.

import PropTypes from 'prop-types'

export const userShape = PropTypes.shape({
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired,
  email: PropTypes.string.isRequired,
  phone: PropTypes.number.isRequired
})

import React from 'react'
import { userShape } from 'models/user'

const User = ({ user }) =>
  <div>
    My name is: {user.name}
    I am {user.age} years old
  </div>

User.propTypes = {
  user: userShape.isRequired
}

export default User

This way the user “structure” will be isolated in our model and our component will not have to worry about it, the only thing that it will need to do is show the data from props and if some of them are wrong, then React will tell us. So if we need to change or add some of our user attributes, we just need to add this on userShape and it will be validated in all components that receive user as props. Note that isRequired is not used inside the model, but rather, inside the component, this way we can use different settings for different components.

So that way we can maintain our React application with really good consistency, and avoiding future problems with the prop types.

What’s the catch?

But life is not a bed of roses :/. Extracting proptypes to a separate file adds the caveat that eslint-plugin-react’s prop-types rule won’t consider the extracted type when linting your component. This is not the end of the world but it is a tradeoff you should consider if you are willing to pay before adopting the technique described in this post.

Conclusion

If you want to make sure that your props are being received in your React Components with the right type, you can use this strategy. All these advises and patterns are based on self-experience and it’s been working fine for me, but of course, it’s not the only way to do this. If you use it them a different way, please leave it up in the comments.

Thanks to Talysson de Oliveira, Iago Dahlem Lorensini, Vitor Talaia, Laka3000, and Gabriel Muller.