Embracing the Single Responsibility Principle in My Stack Overflow App

Brijen MakwanaBrijen Makwana
5 min read

Before delving into my project, let's first discuss what SRP is and why it's important.

The Single Responsibility Principle (SRP) says that each class should focus on doing just one thing. This makes it easier to understand and work with the code. When a class has only one job, changes to the code are less likely to mess up other parts of the program. It's like organising your tools in a toolbox: each tool has its own job, and it's easier to find what you need when everything is in its place. So, SRP helps keep our code organised and less confusing.

In my Stack Overflow mobile app, I've utilised Tanstack Query for data fetching and created several custom components to compose the entire UI. Here's the code for the Home screen responsible for rendering the list of featured questions.

import { useState } from "react";
import { RefreshControl } from "react-native";
import { FlashList } from "@shopify/flash-list";
import { darkColors } from "@tamagui/themes";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";

import Error from "../../components/Error";
import { MyStack } from "../../components/MyStack";
import QuestionCard, { IQuestion } from "../../components/QuestionCard";
import Sort from "../../components/Sort";
import {
  FEATURED_QUESTIONS_SORTING_OPTIONS,
  SORTING_ORDERS
} from "../../constants/sorting";

const Home = () => {
  const [sort, setSort] = useState<string>(
    FEATURED_QUESTIONS_SORTING_OPTIONS[0]
  );
  const [sortingOrder, setSortingOrder] = useState<string>(SORTING_ORDERS[0]);

  const getFeaturedQuestions = async () => {
    const response = await axios.get(
      "https://api.stackexchange.com/2.3/questions/featured?",
      {
        params: {
          order: sortingOrder,
          sort: sort,
          site: "stackoverflow",
          filter: "!nNPvSNP4(R",
          key: process.env.EXPO_PUBLIC_API_KEY
        }
      }
    );

    return response.data.items;
  };

  const {
    isPending,
    error,
    refetch,
    data: questions
  } = useQuery({
    queryKey: ["questionsData", sort, sortingOrder],
    queryFn: getFeaturedQuestions
  });

  if (error) return <Error />;

  return (
    <MyStack>
      {questions?.length > 0 && (
        <Sort
          sort={sort}
          setSort={setSort}
          sortingOrder={sortingOrder}
          setSortingOrder={setSortingOrder}
          data={FEATURED_QUESTIONS_SORTING_OPTIONS}
        />
      )}

      <FlashList
        data={questions as IQuestion[]}
        renderItem={({ item }) => (
          <QuestionCard
            {...item}
            isBody
          />
        )}
        estimatedItemSize={5}
        contentContainerStyle={{
          paddingHorizontal: 10
        }}
        refreshControl={
          <RefreshControl
            refreshing={isPending}
            colors={[darkColors.green11]}
            progressBackgroundColor={darkColors.gray5}
            onRefresh={refetch}
          />
        }
      />
    </MyStack>
  );
};

export default Home;

lets breakdown what this component is doing.

  1. State Management:

    • sort and sortingOrder are state variables initialised using the useState hook. They manage the sorting criteria and order of the questions, respectively.
  2. Data Fetching:

    • getFeaturedQuestions is an asynchronous function that fetches featured questions from the Stack Exchange API based on the specified sorting criteria and order.

    • The useQuery hook is used to manage the data fetching process. It takes an object with queryKey and queryFn properties. queryKey is an array representing the query parameters, and queryFn is the function responsible for fetching the data.

  3. Rendering:

    • Conditional rendering based on the presence of error. If an error occurs during data fetching, it renders the Error component.

    • The main JSX structure renders a Sort component (if questions exist) and a FlashList component.

    • Inside the FlashList component, it renders individual QuestionCard components for each question item.

This component is handling multiple responsibilities, which makes it prone to changes and increases its complexity. Ideally, a component should have a single responsibility, but in this case, it combines data fetching, rendering, and potentially other tasks. Consequently, any modifications to these functionalities would require altering this component, leading to longer code and unnecessary cognitive load for developers.

Implementing SRP

Initially, I analysed my code to identify its core responsibilities. It primarily handled two tasks:

  1. Data fetching

  2. Component rendering

Recognising this, I realised the need to segregate these responsibilities. To address data fetching specifically, I crafted a custom hook dedicated solely to retrieving the list of featured questions. take a look.

  1. For JavaScript logic reuse, craft a distinct JavaScript function.

  2. For JSX code reuse, formulate a custom component.

  3. When it comes to reusing JavaScript logic incorporating hooks, devise a custom hook.

import { useQuery } from "@tanstack/react-query";
import axios from "axios";

import { IQuestion } from "../types";

const useFeaturedQuestions = (sortingOrder: string, sort: string) => {
  const getFeaturedQuestions = async (): Promise<IQuestion[]> => {
    const response = await axios.get(
      "https://api.stackexchange.com/2.3/questions/featured?",
      {
        params: {
          order: sortingOrder,
          sort: sort,
          site: "stackoverflow",
          filter: "!nNPvSNP4(R",
          key: process.env.EXPO_PUBLIC_API_KEY
        }
      }
    );

    return response.data.items;
  };

  const { data, ...rest } = useQuery({
    queryKey: ["questionsData", sort, sortingOrder],
    queryFn: getFeaturedQuestions
  });

  return {
    questions: data,
    ...rest
  };
};

export default useFeaturedQuestions;

This code defines a custom hook, useFeaturedQuestions, which fetches featured questions from the Stack Exchange API based on sorting criteria and order parameters. It encapsulates data fetching logic using axios and the useQuery hook from @tanstack/react-query, returning an object with the fetched questions and query-related properties for reusability in components.

Now, i have used this custom hook in my main component that renders list of features questions.

import { useState } from "react";
import { RefreshControl } from "react-native";
import { FlashList } from "@shopify/flash-list";
import { darkColors } from "@tamagui/themes";

import Error from "../../components/Error";
import { MyStack } from "../../components/MyStack";
import QuestionCard from "../../components/QuestionCard";
import Sort from "../../components/Sort";
import {
  FEATURED_QUESTIONS_SORTING_OPTIONS,
  SORTING_ORDERS
} from "../../constants";
import useFeaturedQuestions from "../../hooks/useFeaturedQuestions";

const Home = () => {
  const [sort, setSort] = useState<string>(
    FEATURED_QUESTIONS_SORTING_OPTIONS[0]
  );
  const [sortingOrder, setSortingOrder] = useState<string>(SORTING_ORDERS[0]);

  const { questions, isFetching, isError, refetch } = useFeaturedQuestions(
    sortingOrder,
    sort
  );

  if (isError) return <Error refetch={refetch} />;

  return (
    <MyStack>
      {questions?.length > 0 && (
        <Sort
          sort={sort}
          setSort={setSort}
          sortingOrder={sortingOrder}
          setSortingOrder={setSortingOrder}
          data={FEATURED_QUESTIONS_SORTING_OPTIONS}
        />
      )}

      <FlashList
        data={questions}
        renderItem={({ item }) => (
          <QuestionCard
            {...item}
            isBody
          />
        )}
        estimatedItemSize={5}
        contentContainerStyle={{
          paddingHorizontal: 10
        }}
        refreshControl={
          <RefreshControl
            refreshing={isFetching}
            colors={[darkColors.green11]}
            progressBackgroundColor={darkColors.gray5}
            onRefresh={refetch}
          />
        }
      />
    </MyStack>
  );
};

export default Home;

Now that the code adheres to the Single Responsibility Principle (SRP), you can emphasise its improved clarity and maintainability. By separating concerns, it becomes easier to understand and modify. The Home component now solely focuses on rendering UI elements, while the data fetching logic is encapsulated within the useFeaturedQuestions hook. This division of responsibilities enhances code organization and facilitates easier testing and future enhancements. Additionally, any modifications to the data fetching process won't impact the rendering logic, promoting code stability and reusability.

To explore similar implementations, feel free to visit the GitHub repository for my Stack Overflow app.

You can download the app from Play Store.

0
Subscribe to my newsletter

Read articles from Brijen Makwana directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Brijen Makwana
Brijen Makwana

I'm a Mobile & Web Developer, React-Native, React, Typescript, and Javascript expert, YouTube Creator, GeeksForGeeks Mentor, and Book Author. πŸ•ΈοΈπŸ’‘πŸ“š