DEV Community

Renan Moreira
Renan Moreira

Posted on • Edited on

SOLID in React- SRP - Single Responsibility Principle

Embora tenha sido concebido para a programação orientada a objetos, os princípios do SOLID podem ser aplicados em outros contextos como em componentes React.

  • Este post aborda o "S" do [S]OLID

  • Criado com Next 13 + React 18

  • Codepen no final do post

SRP - Single Responsibility Principle

O Single Responsibility Principle (Princípio da Responsabilidade Única) é um dos cinco princípios do SOLID, que é uma abordagem para escrever código limpo, modular e escalável. O princípio SRP estabelece que uma classe ou módulo deve ter apenas uma razão para mudar, ou seja, deve ter apenas uma responsabilidade.

No código abaixo, o componente está lidando com a obtenção de dados da API, o gerenciamento do estado do componente e a estilização do componente. Isso torna o componente difícil de manter e entender, pois tem muitas responsabilidades.

/**
 * components
 *  examples
 *    solid
 *      srp
 *        ListUsersAfter
 *          index.tsx
 */
Enter fullscreen mode Exit fullscreen mode
// srp/ListUsersAfter/index.tsx

'use client';

import axios from 'axios';
import { useEffect, useState } from 'react';

type User = {
  name: {
    first: string;
  };
  email: string;
  gender: 'male' | 'female';
};

type Data = {
  results: User[];
};

export const ListUsersBefore = () => {
  const [data, setData] = useState<Data>();
  const [isLoading, setIsLoading] = useState(false);

   const handleFetch = async () => {
    setIsLoading(true);
    const url = 'https://randomuser.me/api/?results=5';
    const res = await axios.get(url);
    setIsLoading(false);
    setData(res.data);
  };

  useEffect(() => {
    handleFetch();
  }, []);

  return (
    <>
      {isLoading && <div>Loading...</div>}

      {data?.results?.map((user) => (
        <div
          key={user.email}
          style={{
            color: user.gender === 'male' ? 'blue' : 'red',
            border: '1px solid',
            maxWidth: '200px',
            padding: '0.2rem',
            marginBottom: '0.2rem',
            borderRadius: '4px'
          }}
        >
          {user.name.first}
        </div>
      ))}
    </>
  );
};

Enter fullscreen mode Exit fullscreen mode

Para resolver esse problema e aderir ao princípio SRP, podemos dividir o componente em vários componentes menores e mais focados em suas responsabilidades.

/**
 * components
 *  examples
 *    solid
 *      srp
 *        ListUsersAfter
 *          hooks
 *            useUsers
 *              index.ts
 *          index.tsx
 *          styles.ts
 */
Enter fullscreen mode Exit fullscreen mode

O estilo que antes estava dentro do componente principal foi removido e isolado em outro componente.

//srp/ListUsersAfter/styles.ts
import styled from 'styled-components';

type StyledUserProps = {
  gender: 'male' | 'female';
};

export const S = {
  Div: styled.div<StyledUserProps>`
    color: ${(props) => (props.gender === 'male' ? 'blue' : 'red')};
    border: 1px solid;
    max-width: 200px;
    padding: 0.2rem;
    margin-bottom: 0.2rem;
    border-radius: 4px;
  `
};
Enter fullscreen mode Exit fullscreen mode

Agora, o componente principal tem apenas a responsabilidade de renderizar a lista de usuários.

//srp/ListUsersAfter/index.tsx

import { useUsers } from './hooks/useUsers';
import { S } from './styles';

export const ListUsersAfter = () => {
  const { data, isLoading } = useUsers();

  return (
    <>
      {isLoading && <div>Loading...</div>}

      {data?.map((user) => (
        <S.Div gender={user.gender} key={user.email}>
          {user.name.first}
        </S.Div>
      ))}
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

A responsabilidade de adquirir os dados foi isolada em um componente na pasta hooks. Além disso, houve melhorias nas tipagens, a aplicação da técnica debounceTime e o tratamento de erros, embora esta última técnica não faça parte do SOLID.

//srp/ListUsersAfter/hooks/useUsers/index.ts

import { sortObjects } from '@/app/utils/sortObjects';
import axios from 'axios';
import { useCallback, useEffect, useState } from 'react';

type User = {
  name: {
    first: string;
  };
  email: string;
  gender: 'male' | 'female';
};

export type useUsersProps = {
  debounceTime?: number;
  sortOrder?: 'asc' | 'desc';
  limit?: number;
};

type useUsersReponse = {
  data: User[];
  isLoading: true | false;
  error: null | string;
};

export const useUsers = ({
  debounceTime = 500,
  sortOrder = 'asc',
  limit = 5
}: useUsersProps = {}): useUsersReponse => {
  const [data, setData] = useState<User[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<null | string>(null);

  const handleFetch = useCallback(async () => {
    setIsLoading(true);

    try {
      const url = `https://randomuser.me/api/?results=${limit}`;
      const res = await axios.get(url);

      setData(sortObjects(res.data.results, 'name.first', sortOrder));
      setError(null);
    } catch (err) {
      setError('Error get Users!');
    }

    setIsLoading(false);
  }, [sortOrder, limit]);

  useEffect(() => {
    const timer = setTimeout(() => {
      handleFetch();
    }, debounceTime);

    return () => {
      clearTimeout(timer);
    };
  }, [debounceTime, handleFetch]);

  return {
    data,
    isLoading,
    error
  };
};

Enter fullscreen mode Exit fullscreen mode

Devido à limitação da plataforma, todo o exemplo está contido em um único arquivo, sem a possibilidade de separação em pastas e arquivos distintos. Entretanto, ao longo do post, as responsabilidades serão abordadas em camadas distintas.

Top comments (0)