Kumar Harsh Technical writer and software developer based in India.

Understanding inheritance in React Native

8 min read 2285

Understanding Inheritance React Native

Code reusability is one of the strongest pillars of modern application development; without the ability to reuse code in multiple places while still maintaining its integrity, developing applications of large scale would be impractical. Inheritance is a software programming concept that enables code reuse on a wide scale.

In this article, we’ll explore inheritance and learn how to implement it in React Native. Later in the article, we’ll discuss some other popular methods of code reuse and see how they fare against inheritance. Let’s get started!

What is inheritance?

Inheritance is one of the fundamental concepts of object-oriented programming. It enables a class to be derived from another class and carry over its property and methods. Access to the base class’s properties and methods can be controlled via access modifiers, and new properties and methods can be added easily to implement custom business logic.

To understand it better, let’s run through an example. Imagine you have the following JavaScript class:

class Car {

 constructor() {
   this.wheels = 4
   this.color = "RED"
 }
  accelerate () {
   // Logic to accelerate here
   console.log("accelerate called") 
 }
  stop () {
   // Logic to stop vehicle
   console.log("stop called")
 }

 honk () {
   // Logic to honk the horn
   console.log("honk!")
 }

 printInfo() {
   console.log("Wheels: " + this.wheels + " Color:  " + this.color)
 }
}

Let’s say you wanted to create a similar class for a bus. Now, you know that it would probably have the same functions for accelerating, stopping, and honking. However, it would have a different number of wheels, let’s say 8, and possibly a different color. It could also perform an additional function, like opening the passenger doors.

The code below shows how to implement it easily by inheriting all of Car’s properties and methods:

class Bus extends Car {
 constructor() {
   super()
   this.wheels = 8
   this.color = "BLUE"
 }

 openPassengerDoors () {
   // Logic to open passenger doors here
   console.log("openPassengerDoors called")
 }
}

The extends keyword enables the Bus class to extend the features of the Car class and add its own. You can check it out for yourself:

// Create new instances of both classes
const car = new Car()
const bus = new Bus()

// Print their properties' values
car.printInfo()
// Output: Wheels: 4 Color:  RED
bus.printInfo()
// Output: Wheels: 8 Color:  BLUE

// Call the accelerate method
car.accelerate()
// Output: accelerate called
bus.accelerate()
// Output: accelerate called

// Call the newly created method
bus.openPassengerDoors()
// Output: openPassengerDoors called

With this approach, inheritance makes it simple for you to reuse existing logic without having to rewrite it all over again. Some of the other benefits of using inheritance include:

  • Flexibility to write and update the base code in one place
  • Saves time and effort that would have been spent rewriting the same code again
  • Provides a clear structure of relationships between classes
  • Allows for access modifiers to control how the inherited data is used by the children’s classes

What is React Native?

React Native is one of the most popular hybrid application development frameworks in 2022. Based on JavaScript, React Native can help you create apps quickly for both Android and iOS using the same codebase.

Inspired by React, another popular JavaScript framework that is used to develop web applications, React Native was created by Facebook in 2015 and has since been open-sourced. React Native uses the concept of components to scaffold and compose complex UI screens.

Since each app can contain up to hundreds of components, it is important to organize them well and draw meaningful relationships, helping to keep maintenance efforts low and making it easy to push updates in the future.

This is where inheritance comes in. Now, let’s learn how we can implement inheritance in React Native.

Implementing inheritance in React Native

Similar to the JavaScript example above, you can use the extends keyword to implement inheritance in React Native. To understand it better, let’s start with an example. Below is a component that displays a text box with the chosen text, text color, and background color:

import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';

export default class TextPrompt extends React.Component {
  constructor(props) {
   super()

   // Store the text from props in state
   this.text = props.text

   // Create and store the styles object in state
   this.styles = StyleSheet.create({
       container: {
         alignItems: 'center',
         justifyContent: 'center',
         padding: 12,
         borderRadius: 4,
         backgroundColor: "white"
       },
       paragraph: {
         margin: 2,
         fontSize: 14,
         color: "black",
         fontWeight: 'bold',
         textAlign: 'center',
       },
     }
   );
 }

 render() {
   return (
       <View style={this.styles.container}>
         <Text style={this.styles.paragraph}>
           {this.text}
         </Text>
       </View>
     );
 }
 }

You can use this component in your App.js file as follows:

export default function App() {
 return (
   <View>
     <TextPrompt text="This is a text box"/>
   </View>
 );
}

On a mobile screen, the TextPrompt would look like the following image:

Appjs TextPrompt Component View

Now, let’s say you wanted to use this text box to display an error message to your users. A simple redesign would include changing the background color to red and the text color to white so it looks like the following image:

Textprompt Error Message View

You can extend the TextPrompt component to create another component called ErrorPrompt with the following code:

import TextPrompt from "./TextPrompt"
import { StyleSheet } from "react-native"

export default class ErrorPrompt extends TextPrompt {

 constructor (props) {
   super(props)

   // Create and store the styles object in state
   this.styles = StyleSheet.create({
       container: {
         alignItems: 'center',
         justifyContent: 'center',
         padding: 12,
         borderRadius: 4,
         backgroundColor: "red"
       },
       paragraph: {
         margin: 2,
         fontSize: 14,
         color: "white",
         fontWeight: 'bold',
         textAlign: 'center',
       },
     }
   );
}
}

You’ll notice that this component does not have a render method; it inherits the render method from the parent class. Therefore, you cannot make changes to the original render method; you can either reuse it completely or rewrite it from scratch.

However, rewriting the render method from scratch would not make sense since you’d have to know the internal design and structure of the TextPrompt class. Since it’s not practical to rewrite the render method, adding new state variables or making changes to them would be no use either.

This brings us to the revelation that inheritance in React Native is not a recommended or useful practice. While inheritance is known to provide great ease by reusing code in most programming languages, it just doesn’t work in React Native because of how React Native components are structured.



Comparing inheritance with other methods of code reuse

So, if inheritance is not the right way to reuse React Native code, what is? Let’s take a look at some other methods for reusing code in React Native.

Props

Props are the default way of turning a component into an independent, reusable building block for a UI. React Native and its superclass, React, recommend using props wherever possible to pass arguments to a component and have it change its behavior based on the value of the component.

A prop can be of any data type, number, string, object, etc. In fact, we already used props in our TextPrompt example to pass the value of text to it.

The code below shows how you can configure passing the promptType in your TextPrompt to turn it into an error prompt when needed:

import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';

export default class TextPrompt extends React.Component {
  constructor(props) {
   super()

   // Store the text from props in state
   this.text = props.text

   // Create and store the styles object in state
   this.styles = StyleSheet.create({
       container: {
         alignItems: 'center',
         justifyContent: 'center',
         padding: 12,
         borderRadius: 4,
         backgroundColor: props.promptType === "error" ? "red": "white"
       },
       paragraph: {
         margin: 2,
         fontSize: 14,
         color: props.promptType === "error" ? "white" : "black",
         fontWeight: 'bold',
         textAlign: 'center',
       },
     }
   );
 }

 render() {
   return (
       <View style={this.styles.container}>
         <Text style={this.styles.paragraph}>
           {this.text}
         </Text>
       </View>
     );
 }
 }

You can use it in your App.js component as follows:

export default function App() {
 return (
   <View style={styles.container}>
    <TextPrompt text="This is a normal message"/>
  <TextPrompt text="This is an error message!" promptType="error"/>
   </View>
 );
}

Now, it will look like the following:

PromptType TextPrompt Error Result

Props are simple to use, can propagate to multiple layers of components based on your needs, and do not put you in complex situations like inheritance does.

Composition

Another way to solve the code reuse problem in React Native is to use composition. Composition takes the props game up a notch by passing in actual components as props to other components. This allows for the creation of new and complex components on the fly.

There is a special prop called children that allows you to turn a component’s tag from empty to non-empty, meaning you can pass data or components directly within the component’s JSX tags. There are two ways that you can use this special children prop.

Containment

Using the children prop, you can pass in a pre-formatted component instead of just textual data and use your existing component as a container. The code below shows how you would rewrite the example from above using the containment method:

import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';

export default class TextPrompt extends React.Component {
  constructor(props) {
   super()

   // Store the children from props in state
   this.children = props.children

   // Create and store the styles object in state
   this.styles = StyleSheet.create({
      container: {
         alignItems: 'center',
         justifyContent: 'center',
        padding: 12,
         margin: 4,
         borderRadius: 4,
         backgroundColor: "white"
      },
       paragraph: {
         margin: 2,
         fontSize: 14,
         color: "black",
        fontWeight: 'bold',
         textAlign: 'center',
       },
     }
   );
 }

 render() {
   return (
       <View style={this.styles.container}>
         <Text style={this.styles.paragraph}>
           {this.children}
         </Text>
       </View>
     );
 }
 }

Here’s how you will use it in your App.js:

export default function App() {
 return (
   <View style={styles.container}>
   <TextPrompt>
       This is a normal message
    </TextPrompt>
    <TextPrompt>
       <Text style={styles.error}>
         This is an error message!
       </Text>
    </TextPrompt>
   </View>
 );
}

You’ll notice that the second TextPrompt now takes in a Text component instead of a simple string. This enables you to pass a styled piece of text to the TextPrompt component, which looks like the following:

Textprompt Takes Text Component

We had to redesign the error message to be in red text over a white background instead of white text over a red background. When passing in a custom component through composition, you don’t have control over the receiving component’s properties.

It takes your components and renders them in the place that you asked it to using props.children. This is a significant characteristic of this method, and it is recommended not to use containment where you need the container’s behavior to be modified based on its children. However, if you’re looking to build reusable, multi-purpose containers, containment is a great way to go.

Specialization

Another way to use composition to your advantage is to build specialized components by reusing existing ones.

Throughout this article, we’ve discussed a text prompt and an error prompt. While a text prompt is a generic component that can be used to show any kind of text, an error prompt is a specialized version of it that is only meant to show errors. Therefore, an error prompt makes the perfect example of a specialized component. Here’s how you can implement specialization in the same example as above.

The TextPrompt will look like the following:

import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
export default class TextPrompt extends React.Component {
 constructor(props) {
  super()
  // Store the children from props in state
  this.children = props.children
   // Create and store the styles object in state
  this.styles = StyleSheet.create({
      container: {
        alignItems: 'center',
        justifyContent: 'center',
        padding: 12,
        margin: 4,
        borderRadius: 4,
        // Instead of relying on any internal calculations, the background color is now directly controlled via props
        backgroundColor: props.backgroundColor || "white"
      },
      paragraph: {
        margin: 2,
        fontSize: 14,
        // Instead of relying on any internal calculations, the text color is now directly controlled via props
        color: props.textColor || "black,
        fontWeight: 'bold',
        textAlign: 'center',
      },
    }
  );
}
render() {
  return (
      <View style={this.styles.container}>
        <Text style={this.styles.paragraph}>
          {/* We're still using composition, so children will be rendered as they're passed in*/}
          {this.children}
        </Text>
      </View>
    );
}
}

You’ll now create the new, specialized component called ErrorPrompt that reuses TextPrompt internally:

import React from "react"
import TextPrompt from "./TextPrompt"

export default class ErrorPrompt extends React.Component {

 constructor (props) {
   super()
   // Store the children in state
   this.children = props.children
 }

 render() {
   // Reuse the TextPrompt component to render a specialized ErrorPrompt
   return <TextPrompt textColor="white" backgroundColor="red">{this.children}</TextPrompt>
 }
}

Now, it becomes really simple to use:

export default function App() {
 return (
   <View style={styles.container}>
    <TextPrompt>
      This is a normal message
    </TextPrompt>
    <ErrorPrompt>
       This is an error message!
    </ErrorPrompt>
   </View>
 );
}

Notice how simple it is to use the error component now! The calling environment does not need to worry about the internal details of how the error prompt works, and it looks just right:

Specialized Errorprompt Component Reuse Text

Specialized components are great when it comes to reusing code for certain fixed scenarios. However, identifying when and when not to opt for specialized components requires some experience and expertise on the developer’s part.

Final thoughts

Inheritance is a great OOP concept to reuse code while maintaining its integrity. However, due to the structure of React Native components, inheritance is not the perfect option when it comes to React Native.

In this guide, you learned about implementing inheritance in React Native. We also explored its downsides, then reviewed some alternate methods for reusing code while avoiding the complexity that inheritance brings, like props, composition, containment, and specialization.

I hope you enjoyed this article. Leave a comment and let me know which method you prefer for reusing React Native code.

LogRocket: Instantly recreate issues in your React Native apps.

LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.

LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.

Start proactively monitoring your React Native apps — .

Kumar Harsh Technical writer and software developer based in India.

Leave a Reply