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.
State Management:
sort
andsortingOrder
are state variables initialised using theuseState
hook. They manage the sorting criteria and order of the questions, respectively.
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 withqueryKey
andqueryFn
properties.queryKey
is an array representing the query parameters, andqueryFn
is the function responsible for fetching the data.
Rendering:
Conditional rendering based on the presence of
error
. If an error occurs during data fetching, it renders theError
component.The main JSX structure renders a
Sort
component (if questions exist) and aFlashList
component.Inside the
FlashList
component, it renders individualQuestionCard
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:
Data fetching
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.
For JavaScript logic reuse, craft a distinct JavaScript function.
For JSX code reuse, formulate a custom component.
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.