I recently stumbled upon a React Native library so perfect for my needs that I would have been silly not to use it…
You see: I needed to add pinch and drag gesture support to the React Native app I’m building. It’s totally possible, albeit entirely impractical, to do so using just the code that ships with React Native. This library abstracted things so that all I needed to do was:
Unfortunately, it was last updated 9 months ago and isn’t compatible with new React Native applications. I set about refactoring the code so that it would work again (in the hope that the author accepts the changes and tags a new version). Here are some of the things I had to do:
Including yarn.lock and excluding development files
This is probably the most contentious change of all, but a significant one to allow for better collaboration. Lock files only matter to the top-level project they’re in, and then only if someone is trying to make changes to that project. Inside
node_modules they don’t matter.
I decided to include this file so that new contributors can get up and running sooner. I won’t go into too much detail about how they work, save to say that they make the process of installing dependencies faster and more consistent.
To offset this change, I also included a
.npmignore file, which lists a few files and folders that should be excluded when the library is installed as a dependency:
This means the library will be faster to install (as a dependency), as these files won’t be part of that installation process.
The library was using Mocha (and a bunch of other things) to test. Jest has come a long way in the past year, and seemed like a good choice when deciding to add snapshot tests and mocking. I migrated the tests using the recommended
jest-codemods command (described at https://facebook.github.io/jest/docs/en/migration-guide.html).
I selected the option to auto-detect which libraries to migrate from. Then I had to go through each file and clean up a few things, like how the mocks were defined. I find Jest’s error messages to be excellent, so finding the things I needed to change was quite painless.
In the process, I learned a bit more about Sinon, mocking, and the kinds of assertions I can make inside Jest tests. I also took the opportunity to add and improve some of the tests. I got test coverage up to about 36% of all statements. I have no idea what it was before, since the library had no tooling for test watching or coverage reporting before I got stuck in.
There’s still a lot more work I could do, in this area. I’d like to see what happens with the pull request before I spend more time on this.
The library previously used Standard code style. I don’t mind, one way or the other, what code style is used — because I consider manual code style enforcement a waste of time. Don’t get me wrong — I love the idea of code style enforcement and I think it’s good for teams to format code in a consistent way. I just think there’s excellent tooling we could be using instead of reviewing, complaining about, and manually re-formatting code to match a code style.
Prettier has emerged as the standard way (if you’ll pardon the pun) to format code. I added a folder of hooks, which I like to use to automatically format and test my code, before commits and pushes. I also added
yarn format and
yarn test commands to provide easier access to these features.
My preferred Prettier modifiers are:
ES6 components vs. React.createClass
Others have dealt extensively with this issue. Go read their things as well!
The recommended way to create new React component classes is with ES6 class syntax. To give you an example of what some of these components looked like before:
This approach now triggers warnings and has some secondary side-effects that affect how we structure libraries. The biggest problem is that this syntax used to support mixing; which were a way to share methods from different classes. ES6 classes don’t support these, and the community consensus is that mixins are bad and composition is good. Before we look at how that works, let me show you what the updated version of this component looks like:
It’s still possible to group
defaultProps within the class definition, and aside from some changes to the properties
GestureView now accepts and requires, the component remains much the same as it was before.
So, what about those mixins? As I mentioned, the recommended thing to do is to replace mixins with higher-order components (or HoC as they’re often referred to). This involves re-working how these mixin functions work. Let me demonstrate, but comparing one of the previous mixins to a new higher-order component. This is what that
events(['onLayout']) mixin looked like:
All it was meant for was to create zero or more
Rx.Subject objects. If you’re unfamiliar with RxJS, you can think of these as a kind of event emitter, where events can be triggered on an
This code was supposed to do a few things:
- Create and assign
Rx.Subjectobjects to a
- Assign that object to the component this mixin is applied to, both as top-level properties and as children of a
- Stop emitting events to these subjects after the component has unmounted.
Once applied to a component, this mixin’s methods would be called after the component’s methods. That means the component (to which this mixin was added) would be able to use streams inside
Now, let’s consider what this could look like, as a higher-order component:
Higher-order components are created by invoking a function which returns a new component. The new component is a decorator to the original. In general programming terms, a decorator replaces the thing that is decorated. When methods are called, on the decorator, they are proxied to the decorated object.
In React, this works slightly differently; as well the proxying happens through render (and after state changes trigger subsequent renders).
So, each time the state of this decorator component changes, the decorated component will be re-rendered. The new version still creates streams (what I’ve taken to calling the subjects) but it assigns them as props to the decorated component.
When we want to apply these decorators, we wrap the component class in function calls to the decorator functions:
This order ensures that an
evented object is created, which then mounts a
draggable object, which then mounts the
GestureView object. These decorations and mounting happen when the default exported function is called.
Ok, if that hasn’t confused you then you are a genius! I didn’t come up with this idea, and it seems I have to re-learn it every time I try to use the concept…
Migrate to PropTypes library
A recent (and relatively small) change is that PropTypes have moved into a separate library. This is probably because people are now writing FlowType and TypeScript React components; which include a different way of type-checking component properties.
Drop support for image type
This change is less to do with refactoring in general, and more to do with the specific library. It allows views (any children) or images (where an image will be rendered). I’m not a fan of the special handling of images, in this case. It seems cleaner to me to require that consumers create the child components most fitting to their situation. It also reduces the code to debug and maintain…
Re-arrange imports and exports
I also took the opportunity to rearrange the imports and exports to group things more consistently. The current version uses:
…while the new version uses:
This is slightly more code, and requires changes to the examples, but I think the effort is worth it. Things are less likely to collide or be confused with each other. Where before I was asking; “is
create a gesture?”, I now know for sure what is what.
Except I don’t really understand exactly what the gestures are doing, but that’s a discussion for another time…
I had a lot of fun reworking this library. It is great, and I think it just needed a bit of love with all the recent changes to the React Native ecosystem. I hope the author likes the changes. If not, I guess this’ll be a new fork of the work (with all the appropriate credit, of course).
I mentioned wanting to spend more time with the tests, and I think the same can be said of the docs. This is a fascinating abstraction, if given the attention it deserves.
Have you had similarly fun experiences, refactoring old code? Tell me about it…
After using and working with this refactored code, for a considerable amount of time, I realized that it would be much cleaner to just remove the higher-order components.
It’s not that I think higher-order components are bad, or that you should never use them. They’re the best way to replace the concept of mixins and they can bring a lot of flexibility to a system.
I decided to remove them because of how they were creating dependencies for the
GestureView component. These dependencies aren’t available when
GestureView is mounted, which leads to using
componentWillReceiveProps instead of
componentDidMount. This introduces the same class of bugs as updating state from props.
I’ve adjusted the pull request accordingly.