New to React Native?

The technically inclined amongst us may already know the story of React Native. Developed by Facebook, it is essentially a set of libraries that can communicate with their corresponding APIs. This is where the ‘Native’ tag comes in. By design, React Native is able to easily access features native to the device it is being run on, be it a phone or tablet running Android, iOS, or even Windows, connecting native threads and JavaScript threads through its event-bridge.

React Native uses a mix of JavaScript and XML-like syntax, known as JSX, to render the user interface as a function of the application’s current state. This makes it much more interesting to build component rich UIs with principals like stateful components, a layout engine, virtual DOMs, etc.

Let’s go deep.

Here, at Calcey, React Native is one of our favorite tools to work with. Along the way, we’ve picked up a few tricks useful for scalable react-native app development which we’ll be sharing today.

Write reusable components (But don’t overdo it)

Write reusable components (But don’t overdo it)

React recommends creating reusable components as much as you can. Obviously, this makes maintenance and debugging considerably easier. However, as any experienced coder knows, defining components with too much specificity can actually render them useless. Similarly, defining components too loosely will complicate things.
Take the example of building a screen for an app. A screen is essentially a group of components. Intuitively, it makes sense to write common UI elements such as buttons, lists, etc. as reusable blocks of code. This will not only save time but also make your code cleaner.

Safe coding

Safety is determined by how far the platform will go to prevent the developer from making mistakes when writing applications. Due to the freedom given by JavaScript to decide a coding style based on the preference of the developer, code safety will become an important factor, especially when dealing with scalable apps.

React Native has a few tricks of its own which support Flow and TypeScript to avoid such cases if the developer decides to use them. Flow grants us the ability  to easily add static type checking to our JavaScript. It will also help prevent bugs and allow for better code documentation. Meanwhile, TypeScript will provide great tooling and language services for autocompletion, code navigation, and refactoring. The ecosystem you work in usually has a major influence on helping you decide  what to use, as does your previous exposure to static type systems.

However, Calcey uses these tools to make sure that developers are benefiting from them when it comes to readability of the code, or the code standards.

Extract, extract, extract

React Native projects tend to include a large number of common elements such as styles, images, and global functions (functions that format dates and times, make requests to a server, etc.). At Calcey, we generally encourage our developers to keep such elements separate from the component code. This makes it easier to share elements from anywhere within the app, while also making a given app’s codebase cleaner, and easier to maintain and scale.

Here’s an example of a color.js file coded by one of our developers:

export function hexToRgbA(hex: string, opacity: number) {
  let c;
  if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
    c = hex.substring(1).split('');
    if (c.length === 3) {
      c = [c[0], c[0], c[1], c[1], c[2], c[2]];
    }
    c = `0x${c.join('')}`;

    return `rgba(${[(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',')}, ${opacity})`;
  }

  throw new Error('Bad Hex');
}

Store management

To most React Native developers, Redux is an absolute necessity. But at Calcey, we believe that Redux is not a necessity for the most part. The way we see it, bringing Redux into the picture would be akin to using a hammer to crack open an egg.

Ever since we started using Redux, it has only come in necessary for the most complex of apps where immense scalability is required. To understand this better, consider why Redux was developed in the first place. As Facebook grew to become what was essentially the biggest web-app in the world, it had to contend with the headache of not being able to show the correct number of notifications in the header bar. At the time, it was just difficult for Facebook (or any other web-app) to recognize changes in one part of the app (e.g. when you read a comment on a post) and reflect that change in another area (i.e. reduce the number of unread notifications by one). Facebook wasn’t happy with forcing a web page refresh to solve the problem, so it built Redux as a solution.

Redux works by storing information of an app in a single JavaScript object. Whenever a part of an app needed to show some data, it would request the information from the server, update the single JavaScript object, and then show that data to users. By storing all information in one place, the app always displayed the correct information, no matter where, thereby solving Facebook’s notification problem.

Problems cropped up when other independent developers began using a single object to store all their information—basically every single piece of data provided by the server. This approach has three main drawbacks namely, introducing a need for extra code and creating the problem of ‘stale data,’ whereby unwanted data appears within an app from a previous state and increases the learning curve for new developers.

So how does one overcome this problem? By planning ahead, and using proper requirement identification. If you envision that your app will have extreme scalability issues in the future, it may be better to employ Redux from day one. Otherwise, deploying Redux selectively is wiser. After all, it is possible to apply ideas from Redux without using React. An example of a React component with a local state is given below:

import React, { Component } from 'react';

class Counter extends Component {
  state = { value: 0 };

  increment = (): void => {
    this.setState(prevState => ({
      value: prevState.value + 1
    }));
  };

  decrement = (): void => {
    this.setState(prevState => ({
      value: prevState.value - 1
    }));
  };

  render() {
    return (
      <View>
        <ChildComponent value={this.state.value} />
        <Button onClick={this.increment}>+</button>
        <Button onClick={this.decrement}>-</button>
      </View>
    );
  }
}

We can take these attributes or functions to any depth of the component tree and use them inside those components. This mechanism is called prop-drilling. Be warned though, it’s not a good idea to drill multiple layers unless you have an understanding of where the props are coming from, and where they are going next.

Another solution we can use is the Context API provided by React itself. The Context API allows us to access these props of the parents from any child or a parallel component using the consumer design principal. All these options are used at Calcey, based on the use case.

These are a few of our internal React Native practices and tricks. What are yours? Let us know in the comments below!