Adventures in building for web and native at the same time

HMH is a Learning Company — we build and deliver educational content, games, services, tools and assessments primarily for the kindergarten to the 12th-grade educational market in the US. Those teachers and students have access to a wide and varied range of technologies — they use our products on smart boards, desktops, tablets and mobile devices.
We sell into schools systems right across America, so we have customers with quite varied tech — some have the latest and greatest of everything with a 1 to 1 tech ratio, for others the best tech they have might be their phone. We need to deliver cross-platform.
Our products, therefore, have to work on a broad selection of devices. In the past, we have built web applications with responsive CSS patterns for use on all devices and separate native mobile applications for specific purposes.
We were working on building Ed — a platform that allows us to deliver content and assessments to our students and provide learning insights to our teachers and admins.
We’ve tried doing this before with separate teams — one builds web, one builds a native app and, another one builds a Chrome app. As you can imagine keeping those products aligned turned out to be complicated. Also, if you have one team working in Angular and one in Objective C, it’s hard to share code and, you end up building features multiple times.
So we thought — let us try doing it all with one team and with one language.
How we did it
We ended up being able to use a pretty standard toolset. We had already chosen React as our go-to web development library, and we saw React Native as an excellent opportunity to try this out.
We put everything in the one repository, but we split the code into three primary sections:
- Common — where shared logic lived.
- Native — for native specific logic and views to live.
- Web — for web-specific logic and views to live.
We were working on this a while back, so it predated yarn workspaces, but we were able to use a simple script with symlinking to tie the various packages together. The script would pop into each section, run yarn install and then create a symlink from both ‘web’ and ‘native’ folders so that they could use common as a named dependency.
We built things a component at a time. In the ‘common’ section, we wrote all of our logic as you would typically for a standard web application — we had reducers, selectors, actions and sagas as well as any shared utilities such as our store builder.
We created a higher-order component, HOC, that contained any needed lifecycle methods for the component and any component-specific functionality in common. These HOCs would never contain any device-specific logic or render specific code.

We would then generally create the web view for the component, written in standard React. These components were kept as dumb as possible unless there was a variance between the web and native functionality.
The native view could then be written using React native. We only had to duplicate the view component. We found when we were doing this what we tended to do was copy the web component over to the native folder and replace the element tags with those available in React Native.

Then all you have to do is import the HOC into your view components and hook them up!

Bundle a bunch of those types of components together, and you have an app!

Learnings
We did find that we had to rewrite the styling for both — mainly because for the web we were using CSS and for React Native stylesheets which add an abstraction layer on top of CSS. It didn’t prove to be too much of an issue as the styling of the web and native views were quite different.
Initially, when we were developing, we had one developer working on a feature to build both the web and native versions of that feature at the same time. We started to find that those stories were too big and the mental shift needed to move back and forth between the different capabilities of the view layers was a bit headache-inducing. So, we switched to building the web component and getting that done before the same developer would then open a second story to create the same functionality in a native component. While this did reduce the PR size and let the developer focus on one tech stack at a time, we still found context switching to be a problem.
We had toyed with the idea of splitting the team into two groups, one for each stack, to reduce the context switching. It would have necessitated strong lines of communication between both groups to ensure the logical aspects of components were built with both usages in mind. We felt this might have been the lighter lift, but the project was wound up before we could try it out.
React native itself provided some challenges for us. We found the development process working with emulators and simulators was slow and somewhat frustrating. Though this was some time ago so I would like to reevaluate what the development process is like now. At the time it also had a limited feature set, so we were not 100% convinced if that was the way to go for future mobile development.
While, unfortunately, we didn’t get to build out a platform this way, we did find the pattern quite useful and it might be something we revisit in the future. It wouldn’t necessarily need to be about using React and React native together, but simply about using a single HOC for multiple views. It could be useful if we ever need multiple web targets. Perhaps if we want different view elements for tablet and desktop versions of an application or a basic versus premium user experience.
Does HMH sound like the kind of place you might want to work? We are hiring!