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!
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:
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.
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:
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:
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.
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 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:
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.
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.
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:
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.
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 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.
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 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 — try LogRocket for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.