This post is part of a series on React development for WordPress developers. In my last post, I covered unit testing React components using Jest. Jest, when used for basic assertions and snapshot tests can cover a lot of the functionality of a React component.
The “React Way” of developing interfaces calls for composing interfaces, which are themselves components, out of smaller components. Jest lets us test each component in isolation, but we’ll also need to make sure that component work as intended when nested inside of each other.
This article also covers looping through React components — for example, a Posts component that renders an array of posts using a Post component for each one — using array iterators. In order to speed up development even further, I’ll cover how I use a command line utility to intelligently copy existing components to new components.
Testing Nested Components In A React App
As I wrote earlier, passing props down to multiple components is where React apps get tricky. If you go down to many layers, you end up with the “props drilling problem.”
Prop drilling (also called “threading”) refers to the process you have to go through to get data to parts of the React Component tree.
Kent C. Dodds
I should mention that the new React context API is an alternative approach to this problem. Context API is powerful, but I’d learn this way first, then watch Wes Bos’ video on the context API and think about which problems you have may be better solved with context API then “props drilling.” But for a few layers, this strategy is simple and prop-types and Jest can catch the problems it introduces.
Let’s create a component that loops through an array of posts and then use prop types to safely wire it to the existing Post component.
Using Generact To Copy A Component
At this point, we need a component that is almost the same as our existing one. One way to reduce the amount of repetitive typing we have to do is cut and paste the existing component and then do strategic find and replace. That’s boring and error-prone. Instead, let’s use Generact.
Generact is a module that does basically that, but is built with React components in mind. It’s really neat and saves a ton of time. Generact should be installed globally, which means you only have to do it once, per computer:
Now, we can run the “generact” command in our app to copy any component:
In this screenshot of my IDE, you can see that I switched to the directory with Post, told Generact to copy that component and to put it one directory above. That creates the Posts directory, containing the component Posts and its tests. Neato.
One important note. The fact that it copied the tests is very useful. The fact that it copied the snapshots is not and will cause errors as Jest gets confused about file paths. I generally delete the snapshots folder and then let Jest recreate it. Those snapshots are invalid anyway.
Looping In A React Component
Now we have a start for our component, but it’s the same as the first one. This one should loop posts and pass them to Post. The first thing to do is update the propType for post to be “posts” and contain an array.
While I could use PropTypes.array to specify an array, that’s not what I really want. I do not want any array. I want an array containing objects with the shape of a WordPress post. So I used PropTypes.arrayOf. I could use this to specify, for example, an array containing only strings. In this case, I used the post shape I already had:
This shows the PropType. Once I have my tests working and not before, I will add the loop. Jest lets me work iteratively. First make the component work, then add features. I can do iterate safely knowing what the effects of my changes are because I have the tests first.
I then modified the tests form the Post component to cover this component. In addition to changing from Posts to Post, I changed from a mock post object to a mock array of post objects:
Now that my component works, lets add the loop. One major improvement from ES5 to ES6 and beyond is improved iterators for arrays. Array.forEach() and Array.map() make it easy to iterate over an array like php’s foreach control structure or jQuery’s each method.
The difference between map and forEach is that forEach doesn’t return a value for each iteration. Its useful for validating or mutating each or some items in a collection. On the other hand map, does return a value for each iteration. Therefore forEach is faster than map(), but map() can be used when we need to say return a React component for each item in an array.
That’s exactly what we need — iterate through each post and return a rendered Post component. Here is the loop:
I zoomed in here to look at the loop as the “key” prop is a necessary, but not obvious step. Key is a special prop. React is designed to only update the DOM when needed. That gets tricky with loops.
How does React know if item 3 in the array changed? You may think that React analyzes the array for all of its deeply nested objects and compares them to the last time it saw that array. If you think this, you will be annoyed, like I was when I started learning React and changes in state didn’t re-render. Why? React is NOT comparing the deeply nested properties of an array. We use the “key” prop to signify to React that this is a unique item in the array. Using post ID in this context is great, as its a unique identifier, since that’s what it is.
Here is the whole component:
The tests I already had failed because the snapshot changed when I added the loop. Again, I inspected the change, decided it was what i wanted and then accepted the new spec.
Reusing Prop Types
I know have two components with similar prop types. I have identical code doing very similar things in two places. That smells bad. I knew it was a problem when I did it, but I didn’t want to address it right away. Too many changes at once means test failures are not meaningful. But, this is a problem I want to fix.
Because I have tests in place, when I make the change, I will know that neither component has broken.
To accomplish this, I copied the shape to its own file:
I can then use this as-is for my post prop in the Post component:
I can also use it inside of PropTypes.arrayOf() for the posts prop in the Posts component:
If you’re paying close attention, I added an ID prop to the postShape prop. My description of the post was not complete and is still not. Now, as I expand on it, I need to make a change in one place. By assigning the single responsibility of managing post shape to this constant, I have a maintainable way to change that shape.
What About TypeScript or Flow?
In my last post in this series, I covered setting up prop-types for prop type checking in React. I mentioned why often I think Flow or Typescript is not necessary because of this tool. I do think it’s worth noting that the last step I showed — creating a repeatable shape for a post — that’s basically re-creating TypeScript. TypeScript lets you define the shape — like PHP interface in OOP PHP — for objects.
I’m currently developing npm modules for to share React components between the web app and plugin. For the API client I did use Flow. Since I that module is not using React, and all of my HTTP tests are using mock data, type checking was really important to me.
Testing Events In React Components
So far, we’ve only looked at components that render content, but not update it. What if we want a form? Let’s look at how that works. First, let’s talk about responsibilities. We want to keep this component “dumb” — the logic of updating state is not its concern. It will be passed props and a function to communicate a change in a value to its parent. How its parent works must remain irrelevant to the component.
Keeping your change handlers decoupled is super important for reuse. If your component could get used in a small app using one component to manage state, with Redux or inside of a Gutenberg block, you need to keep the component decoupled from those three systems. Again, the principle of dependency injection applies. The change handler is a dependency from another part of the program, so we pass it into the component.
To begin, I used Generact, the same way as last time, to create a copy of the Post component and called it PostEditor. I then added one new prop-type – onChange.
I used the prop-type for a function and made it required. My snapshot tests immediately created errors as they did not have this prop. To fix this, I updated the tests so the onChange prop was supplied a function that does literally nothing.
These unit tests now prove that the component renders properly when provided the correct components. That’s god, but I would not trust that this means the component can edit a post. We need to test it by simulating the change event on the inputs of the editor.
Form Inputs And Change Handlers For Functional React Components
In my opinion, a component is responsible for shaping the value it passes to its parent via the change handler function. I do not want to have to create a change handler for the title and the content and the author and the taxonomies of the post. That’s too much. I want to pass in the post and a function to call when the post updates.
This means that the component is responsible for taking the event object, extracting its value, and merging that with the original post. That’s a few concerns. This is not where I switch to a class. Yes, I need a few closures, I can do that here.
First, here is our change handler function — internal to the component — that is responsible for merging the update and the existing values:
The PostEdit component started as copy of Post. Let’s update it to have an input for the post title, the same rules of HTML inputs apply here, including passing a function to be called on the inputs change event to the onChange attribute.
Here’s the input:
Here I use a closure to take the event, get its value and create the input for my internal change handler. Now here is the whole component:
This approach is pretty simple and it totally isolates the concern of updating the object of the post inside of this component and then passes all of that out via the changeHandler. That process is black-boxed, but still testable and I don’t have to deal with binding state in a class component and its still simple to read.
You could add more inputs to this component, following the same pattern. That’s the idea, you should try it. Show us what you created with a pull request to the example plugin.