React Native, SOLID, and TypeScript: Improving Your Code Quality

paulocappa

Paulo Messias

Posted on May 9, 2024

React Native, SOLID, and TypeScript: Improving Your Code Quality

Hey devs!

In this article, we'll dive into the SOLID principles to enhance our React Native applications using TypeScript. We'll explore simple ways to make our code more reliable and easier to maintain. Whether you're just starting out or you're a seasoned pro in the programming world, these principles will help take your code to the next level. Ready to level up your code?

1. Single Responsibility Principle

The single responsibility principle states that a function should have only one reason to change.

Bad Practice:

// Bad practice example: a function with multiple responsibilities
function loginUserAndSave(username: string, password: string) {
  // logic to authenticate the user
  // logic to save the user in the database
}
Enter fullscreen mode Exit fullscreen mode

Good Practice:

// Good practice example: separate functions for authenticating and saving the user
function loginUser(username: string, password: string): void {
  // logic to authenticate the user
}

function saveUser(user: User): void {
  // logic to save the user in the database
}
Enter fullscreen mode Exit fullscreen mode

2. Open/Closed Principle

This principle states that software entities should be open for extension but closed for modification.

Bad Practice:

// Bad practice example: a function with conditional logic to draw different shapes
interface Shape {
  type: string;
}

function drawShape(shape: Shape): void {
  if (shape.type === 'circle') {
    console.log("Drawing a circle");
    // logic to draw a circle
  } else if (shape.type === 'square') {
    console.log("Drawing a square");
    // logic to draw a square
  }
}
Enter fullscreen mode Exit fullscreen mode

Good Practice:

// Good practice example: separate functions for drawing generic and specific shapes
interface Shape {
  draw(): void;
}

interface Circle extends Shape {
  // Specific properties of a circle, if needed
  radius: number;
}

function drawShape(shape: Shape): void {
  console.log("Drawing a generic shape");
  // logic to draw the shape
}

function drawCircle(circle: Circle): void {
  console.log(`Drawing a circle with radius ${circle.radius}`);
  // specific logic to draw a circle
}
Enter fullscreen mode Exit fullscreen mode

3. Liskov Substitution Principle

The Liskov substitution principle states that objects of a base type should be replaceable with objects of a subtype without affecting the correctness of the program.

Bad Practice:

// Bad practice example: an interface forcing the implementation of irrelevant methods
interface Bird {
  fly(): void;
  swim(): void;
}

function duck(): Bird {
  const fly = () => {
    // logic to fly
  };

  const swim = () => {
    // logic to swim
  };

  return { fly, swim };
}
Enter fullscreen mode Exit fullscreen mode

Good Practice:

// Good practice example: segregated interfaces for specific behaviors
interface Bird {
  fly(): void;
}

interface Duck extends Bird {
  swim(): void;
}

function mallardDuck(): Duck {
  const fly = () => {
    // logic to fly
  };

  const swim = () => {
    // logic to swim
  };

  return { fly, swim };
}
Enter fullscreen mode Exit fullscreen mode

4. Interface Segregation Principle

This principle states that a class should not be forced to implement interfaces it doesn't use.

Bad Practice:

// Bad practice example: a function implementing unrelated interfaces
interface Worker {
  work(): void;
}

interface Eatable {
  eat(): void;
}

function programmer(): Worker & Eatable {
  const work = () => {
    // logic to work
  };

  const eat = () => {
    // logic to eat
  };

  const sleep = () => {
    // logic to sleep (outside the scope of Worker and Eatable)
  };

  return { work, eat, sleep };
}
Enter fullscreen mode Exit fullscreen mode

Good Practice:

// Good practice example: segregated and separately implemented interfaces
interface Workable {
  work(): void;
}

interface Eatable {
  eat(): void;
}

function programmer(): Workable & Eatable {
  const work = () => {
    // logic to work
  };

  const eat = () => {
    // logic to eat
  };

  return { work, eat };
}
Enter fullscreen mode Exit fullscreen mode

5. Dependency Inversion Principle

This principle suggests that high-level modules should not depend on low-level modules, both should depend on abstractions.

Bad Practice:

// Bad practice example: a function directly depending on a concrete implementation
function switchOn(): void {
  const bulb = lightBulb(); // Directly depending on a concrete implementation
  bulb.toggle();

  const fan = ceilingFan(); // Another example of directly depending on a concrete implementation
  fan.turnOn();
}

// Concrete implementations
function lightBulb() {
  return {
    toggle(): void {
      // logic to turn on/off the light bulb
    }
  };
}

function ceilingFan() {
  return {
    turnOn(): void {
      // logic to turn on the ceiling fan
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

Good Practice:

// Good practice example: a function depending on an abstraction
interface Switchable {
  toggle(): void;
}

function lightBulb(): Switchable {
  return {
    toggle(): void {
      // logic to turn on/off the light bulb
    }
  };
}

function ceilingFan(): Switchable {
  return {
    toggle(): void {
      // logic to turn on/off the ceiling fan
    }
  };
}

function switchOn(device: Switchable): void {
  device.toggle();
}

// Example of using the switchOn function
const bulb = lightBulb();
switchOn(bulb); // Turns on the light bulb

const fan = ceilingFan();
switchOn(fan); // Turns on the ceiling fan
Enter fullscreen mode Exit fullscreen mode

I hope this article has been helpful to you! If you have any questions, just give a shout.

Happy coding!

💖 💪 🙅 🚩
paulocappa
Paulo Messias

Posted on May 9, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related