Reviving a legacy codebase with React Native
For the last couple of months I’ve been helping out a Pokemon Go community organisation with their React Native based mobile app which hadn’t seen active development on it for about a year, so the code base needed a little TLC.
I was given the opportunity to take on developing the app as I’ve got experience with React and a friend of mine had suggested they talk to me but this was my first experience with React Native and I’m always up for learning something new.
React Native Tooling
React Native has a CLI that makes this relatively easy with commands such as
react-native run-ios and
react-native run-android respectively, however I encountered an issue where due to iOS and Xcode being updated since the app was last created I needed to update the dependencies to work with the new version.
A simple update of the React Native framework spiralled into a number of dependencies needing to be updated but eventually everything seemed to be in order and I could start working with the code.
One of the biggest (pleasant) surprises for me when working with React Native over some of the older ‘web tech for native apps’ technologies is the Metro bundle server which allows you to see changes to the app with just a refresh instead of having to recompile the app — this really speeds up development.
Additionally the debug tools for React Native are the same as those web developers are used to, namely Chrome developer tools and
react-devtools which is a standalone version of the Chrome developer tools extension.
Additionally React Native creates Xcode and Gradle projects so you can use Xcode and Android Studio to compile and run the apps and debug the native level functionality.
I unfortunately have not been able to work with Expo which I understand makes things a lot easier but the project was ‘ejected’ which means Expo no longer manages the build cycle for the app as custom native functionality has been added.
Learning the code base
After I got the app running the next step was to get a better understanding of the code base.
The previous development team had done a good job in organising the app into different modules (actions, reducers, components, screens etc) but the components were not as modular as they could have been and there were no tests and limited documentation.
I decided the best course of action was to go through the entire code base and add documentation to everything.
This would allow me to figure out what everything was doing and leave refactor notes for me to pick up later.
While adding doc-strings is relatively easy, turning those doc-strings into a usable documentation site is slightly harder.
I originally started with
jsdoc as I’ve used it in the past but the module structure used in the code base seemed to have confused
jsdoc and I had to use
better-docs to define the module structure manually.
I then experimented with
esdoc2 and the plugins it offers. This worked well as it picked up the module structure and included great tools such as a documentation coverage report and allowed unit test cases to be linked to functions.
I ultimately decided on
jsdoc as I encountered an issue with
esdoc2 where it omitted a number of public methods, from classes which I was unable to configure it correctly to show — not something you want from a documentation generator!
As the code base had no testing in place I started to add some.
Writing tests is by far the best way to understand what a function or class is doing and the previous development team had a number of tickets in their backlog to add tests which allowed me to learn the code and tick of these tickets.
The app uses redux so I started with unit tests covering the action and reducers as when implemented inline with redux’s vision these are pure functions with no side effects which makes them really easy to test.
Once I had written tests for the state management within the app I could then start writing tests for the functions that changed the state and then the components that displayed the data in the state.
As React Native components are just that — components the same approaches to testing them as React components can be taken such as snapshot testing and shallow rendering.
One of the most overlooked aspects of inheriting a project I find as a developer is the project management & product development sides of the code base.
It’s easy as a developer when handed a big code base to get so engrossed in the code that we fail to think about why and how the code came to be written in the first place.
Early on in inheriting the code base I had a meeting with the person who put me to the task of working with it to understand what outstanding pull requests (of which there was eight) could be removed and we were able to reduce the number of feature branches on the repo to just two.
This made things easier as there were less moving parts and more importantly I could see what changes the development team had in the works when they downed tools as there was a chance that ultimately these changes would render some refactoring I was doing redundant.
We also had a backlog grooming session so I could understand what functionality was important, as if they had planned to remove or rewrite something soon then I could stop myself from focusing too much on that area of the app.
These sessions along with the testing were actually the most useful as I got a big picture of the state of affairs as things were left by the previous development team and I knew exactly where to focus my efforts.
Moving to React Native from React
One of the goals of React Native was to bring the web component technology to the native development space to make building native apps quick and easy and I would say it’s definitely met that goal.
Aside from the additional tooling which ultimately is just coordinating the compilation and running of the application shell on a simulator or device the work flow to build an app isn’t too different.
As you’re working with components the same design patterns such as pure components and higher order components can (and should) be used and tools such as Storybook.js can be used to make communication and shared understanding easier between teams.
In fact my next step is to break out a lot of the complex components into smaller pure components that will form a component library imported into the main app so the code base is clearer but also so those pure components can be re-used elsewhere.
This is a pattern I’ve found to work really well in larger React applications as one team can focus on the composition of the app’s data structures and another can focus on the display of that data and the user journeys.
Redux is in use in the app so I’ll most likely refactor it soon to include Redux Saga as there’s a fair amount of side effects in the app (writing state to local storage for instance) that could be abstracted out using Sagas and I’ve found Redux Saga makes these side effects really easy to test.
It’s likely this will be an evolving topic in my blogging for the coming months and potentially years as I’ve still got a lot to learn about React Native and also React.
So far the next steps are to refactor the code base to allow the pure component library to be created, add Redux Saga and set up a Continuous Integration server to run tests as part of the development workflow.
Stay tuned for more insights into React Native!