How We’re Sharing Code Between React and React Native

Robert Vogt
smartive

--

Part of our team following Vitaly Friedman’s Frontend Conf 2017 talk. Photo by Claudio Schwarz

At the Frontend Conf 2017 in Zurich, my colleagues and I keenly listened to João Figueiredo and his talk Framework-agnostic Web Applications with Redux. He talked about how Redux allows his team to build apps and easily switch frameworks without having to rewrite any business logic. In his demonstration he explored how the same Redux store could easily be consumed by a React, an Angular (with NgRx) and a Vue.js app with the correct bindings applied.

During his talk I realised this was exactly the way we already write our React apps; but not because we want to be able to switch frameworks, but for Clean Code and Separation of Concerns reasons. We believe that separating UI from business logic (or Presentational Components from Container Components, as Dan Abramov wrote here) enables a cleaner, easier to test and therefore more maintainable codebase.

One of our next projects is a Social Network with a web frontend and a React Native app being developed alongside. Partially inspired by João’s talk we decided to organise an internal hackday and explore how easily we can share the common domain logic (or the Redux Parts) between the app and the frontend and what quirks we had to expect. Furthermore, we wanted to see what other parts of our codebase we could share and reuse without restricting one of the stand-alone stacks.

We’ve released an example repository of a shared package and how we would use it on GitHub.

Actions and Reducers

Annotated Flux Overview by Facebook: facebook.github.io/flux/docs/in-depth-overview.html

Initially we tried to create the Redux store inside our shared package and only export the store. This made it hard for our apps to extend the store with additional reducers that shouldn’t be shared (e.g. functionality only in the web app). We decided that it should be the client’s responsibility to create its own store and the shared package should only export a set of reducers. This way, the client can easily inject custom reducers alongside the shared reducers into its store:

import { reducers } from ‘your-shared-package’;const store = createStore(combineReducers({
...reducers,
yourCustomReducer: /* (state, action) => state */
}));

There’s not much to say about Actions and Action Creators. Just like our reducers they are pure JavaScript functions and objects and can be used in any package — no matter if React, Angular or something completely different.

You might want to think about most possible use cases and implement actions and reducers accordingly. Adding functionality at a later point means 1) implementing the changes in the shared package 2) releasing the shared package and 3) requiring the latest/correct version in your app. Depending on workflows (Continuous Integration, Code Reviews, and so on) and infrastructure this can take quite a while.

Business Layer

Domain services, repositories, value objects, or generally speaking anything related to business use cases, are usually Vanilla JavaScript and should already be framework-agnostic. The instructions on how to read and write data, what limitations and constraints are in place or how use cases are handled should not be part of a UI layer.

The business layer might (and probably does) even embrace a totally different software architecture. This shouldn’t matter to the UI layer, though. The layers should be able to communicate through clearly defined APIs without being aware of the concrete implementation details.

Components and Styles

Our codebase usually follows the Atomic Design methodology for our component structures. Since both apps would be following the same style guide we thought it would be neat if we could share small, logic-free components like buttons.

Styled Components

With styled-components providing support for React Native it seemed like the perfect candidate for sharing styles between the web and the native app. Our expectation was some sort of transpiler provided by styled-component to normalise React Native StyleSheet and CSS declarations. Unfortunately the library only supports the least common denominator between React Native’s implementation and the CSS standard. Declarations sometimes differ only marginally from React Native to CSS (e.g. no border-shorthand) and other times quite a lot. Either way, it’s too limiting for code sharing and we opted to not share declarations written with styled-components. What we can leverage, though, is its extensibility for theming. One of our application’s main requirements are UI themes based on runtime parameters (e.g. user organisations). With the theming capabilities of the library that’s a wonderful fit and the theme definitions can be shared between the app and the web stack.

Higher Order Components

Working with pure components rather than ES6 classes gives us the impression of cleaner, smaller and better composable components. There are two disadvantages though, and that’s 1) having to put all state into the Redux store and 2) not having lifecycle methods. Higher Order Components can help in these cases.

One UI Design Pattern that has emerged is the use of Skeleton Components. Rather than displaying loading animations, the content’s skeleton is rendered. Baking this logic into into the component itself makes it bloated and contradicts the single-purpose characteristic.

The Higher Order Component is responsible for determining the availability of the content, fetching the remote content and conditionally rendering either the full, or the skeleton component. This way we keep the single-purpose principle intact and have not only a clean code base, but also reusable code. And this is what this article is actually about. These components are all but platform-dependent so they can be shared without any problems.

Writing a Library

A shared page providing functionality and features without actually executing any of these, is just a library. This means a high test coverage and proper static typing are a must. There are many resources on Medium, and online in general, on how to write a good library. Because that’s a topic of its own and too big to handle in this article, we’ll leave it at that.

TL;DR

We are convinced that we can reduce the costs of writing code massively by sharing some of our codebase between a React Native app and a Web App and believe that these parts can easily be shared:

  • Actions, Action Creators and Reducers
  • Business Layer
  • styled-components Theme Configs
  • Higher Order Components

--

--

Robert Vogt
smartive

He tried to look ashamed and succeeded simply in looking pleased with himself. — Neil Gaiman