Book Image

Hands-On Design Patterns with React Native

By : Mateusz Grzesiukiewicz
Book Image

Hands-On Design Patterns with React Native

By: Mateusz Grzesiukiewicz

Overview of this book

React Native helps developers reuse code across different mobile platforms like iOS and Android. This book will show you effective design patterns in the React Native world and will make you ready for professional development in big teams. The book will focus only on the patterns that are relevant to JavaScript, ECMAScript, React and React Native. However, you can successfully transfer a lot of the skills and techniques to other languages. I call them “Idea patterns”. This book will start with the most standard development patterns in React like component building patterns, styling patterns in React Native and then extend these patterns to your mobile application using real world practical examples. Each chapter comes with full, separate source code of applications that you can build and run on your phone. The book is also diving into architectural patterns. Especially how to adapt MVC to React environment. You will learn Flux architecture and how Redux is implementing it. Each approach will be presented with its pros and cons. You will learn how to work with external data sources using libraries like Redux thunk and Redux Saga. The end goal is the ability to recognize the best solution for a given problem for your next mobile application.
Table of Contents (13 chapters)

Presentational components

It's time to learn how to make components reusable. For this goal, we will use the best tool in our hands: the presentational component pattern. It decouples components from logic and makes them flexible.

The presentational component is a pattern name that you will hear very often, if, later on, you decide to use the Redux library. For instance, presentational components are heavily used in Dan Abramov's Redux course.

I like to explain that the presentational component pattern is a website's world. For a long time now, there has been three leading blocks for every website: CSS, HTML, and JavaScript. React, however, introduced a bit of a different approach, that is, the automated generation of HTML based on JavaScript. HTML became virtual. Hence, you may have heard of the Virtual Document Object Model (Virtual DOM). This separation of concerns—HTML (view), CSS (styles), and JavaScript (logic, sometimes called the controller)—should remain untouched in our JavaScript-only world. Therefore, use presentational components to mimic HTML and container components for logic.

Approach this problem in the same fashion in React Native applications. The markup you write should be separated from the logic it consumes.

Let's see this in action. Do you remember Example 4_Stateful expandable component? It has one presentational component already:

const HelloText = ({children, ...otherProps}) => (
<Text {...otherProps}>{children}</Text>
);

This component does not introduce any logic and contains only markup, which is very short in this case. Any logic that can be useful is hidden within props and passed along, as this component does not need to consume it. In more complex examples, you may need to destructure props to pass them to the right components; for example, when using the spread operator above, all props that are not destructured are being passed.

But, instead of focusing on this simple example, let's start refactoring the App component. First of all, we will move the markup to the separate presentational component:

// src/ Chapter_1_React_component_patterns/
// Example_9_Refactoring_to_presentational_component/ App.js
// Text has been replaced with "..." to save space.

export const
HelloBox = ({ isExpanded, expandOrCollapse }) => (
<View style={styles.container}>
<HelloText onPress={() => expandOrCollapse()}>...</HelloText>
<HelloText onPress={() => expandOrCollapse()}>...</HelloText>
{
isExpanded &&
<HelloText style={styles.text}>...</HelloText>
}
</View>
);

Now, we need to replace the render function in the App component with the following:

render = () => (
<HelloBox
isExpanded={this.state.expanded}
expandOrCollapse={this.expandOrCollapse}
/>
);

However, if you run the code now, you will end up with an error on the HelloText press event. This is due to how JavaScript handles the this keyword. In this refactor, we pass the expandOrCollapse function to another object, and there, this refers to a completely different object. Therefore, it cannot access state.

There are a few solutions to this problem, and one is by using the arrow function. I will stick to the best approach performance-wise. It comes down to adding the following line to your constructor:

this.expandOrCollapse = this.expandOrCollapse.bind(this);

There we go; the application is fully functional, just as before. We have refactored one component into two—one presentational and one responsible for logic. Sweet.

Imagine that we had only shallow unit tests of two components.
Would we identify the problem with the this keyword?
Perhaps not. This simple gotcha may catch you in big projects, where you will be too busy to rethink every single component. Watch out and remember integration tests.

Decoupling styles

In the preceding examples, you may have noticed that styles are tightly coupled to presentational components. Why tightly? Because we explicitly include them by using style={styles.container}, but the styles object is not configurable. We cannot replace any style part with props, and that tightly couples us to the existing implementation. In some cases, this is a desired behavior, but in others, it is not.

If you are interested in how styles work, we will deep dive into patterns involving them in Chapter 3, Styling Patterns. You will also learn about the flexbox pattern from CSS and many other conventions.

You will bump into this problem if you have tried to split code into separate files. How can we fix this issue?

Let the styles be the optional prop. If styles are not provided, then we can fall back to the default values:

// src/ Chapter_1/ Example_10_Decoupling_styles/ App.js

export const
HelloBox = ({
isExpanded,
expandOrCollapse,
containerStyles,
expandedTextStyles
}) => (
<View style={containerStyles || styles.container}>
<HelloText onPress={() => expandOrCollapse()}>...</HelloText>
<HelloText onPress={() => expandOrCollapse()}>...</HelloText>
{
isExpanded &&
<HelloText style={expandedTextStyles || styles.text}>
...

</HelloText>
}
</View>
);

Notice the use of the || operator. In the preceding example (expandedTextStyles || styles.text), it first checks if expandedTextStyles is defined and if so returns that value. If expandedTextStyles is undefined, then it return styles.text, which is a default style object that was hard-coded by us.

Now, if we wish, in some places, we can override our styles by passing respective props:

render = () => (
<HelloBox
isExpanded={this.state.expanded}
expandOrCollapse={this.expandOrCollapse}
expandedTextStyles={{ color: 'red' }}
/>
);

This is how we split markup, styles, and logic. Remember to use presentational components as often as possible to make your features truly reusable across many screens/views.

If you come from a backend background, you may quickly jump into assumptions that it is just like the MVC pattern: Model, View, and Controller. It is not necessarily 1:1 relation, but in general, you may simplify it to the following:
  • View: This is a presentational component.
  • Model: This is a data representation, which in our case is the state that is built either in a stateful component or using so-called store and reducers (check Chapter 5, Store Patterns, to learn more details about what Redux is and how to use it).
  • Controller: This is a container component that is responsible for application logic, including event handlers and services. It should be lean and import logic from the respective files.