Dicas de React para Melhorar Instantaneamente seu Código

Dicas de React para Melhorar Instantaneamente seu Código
AdsTerra, Junte-se ao AdsTerra

Índice

  • Retorne funções dos manipuladores
  • Separe responsabilidades
  • Use objetos map em vez de condições
  • Coloque variáveis independentes fora do ciclo de vida do React

Retorne funções dos manipuladores

Se você está familiarizado com programação funcional, sabe do que estou falando. Podemos facilmente chamá-lo de "currying" de alguma forma. Em essência, definimos alguns parâmetros de função antecipadamente.

Temos um problema explícito com o código de boilerplate abaixo. Essa técnica vai nos ajudar!

export default function App() {
  const [user, setUser] = useState({
    name: "",
    surname: "",
    address: ""
  });

  // Primeiro handler
  const handleNameChange = (e) => {
    setUser((prev) => ({
      ...prev,
      name: e.target.value
    }));
  };

  // Segundo handler!
  const handleSurnameChange = (e) => {
    setUser((prev) => ({
      ...prev,
      surname: e.target.value
    }));
  };

  // Terceiro handler!!!
  const handleAddressChange = (e) => {
    setUser((prev) => ({
      ...prev,
      address: e.target.value
    }));
  };

  // E se tivéssemos mais inputs? Deveríamos criar outro handler pra ele?

  return (
    <>
      <input value={user.name} onChange={handleNameChange} />
      <input value={user.surname} onChange={handleSurnameChange} />
      <input value={user.address} onChange={handleAddressChange} />
    </>
  );
}

Solução

export default function App() {
  const [user, setUser] = useState({
    name: "",
    surname: "",
    address: ""
  });

  const handleInputChange = (field) => {
    return (e) => {
      setUser((prev) => ({
        ...prev,
        [field]: e.target.value
      }));
    };
  };

  return (
    <>
      <input value={user.name} onChange={handleInputChange("name")} />
      <input value={user.surname} onChange={handleInputChange("surname")} />
      <input value={user.address} onChange={handleInputChange("address")} />

      {JSON.stringify(user)}
    </>
  );
}

Separe responsabilidades

Criar um componente "Deus" é um erro comum que os desenvolvedores cometem. É chamado de "Deus" porque contém muitas linhas de código difíceis de entender e manter. Eu recomendo fortemente dividir os componentes em conjuntos de submódulos independentes.

Uma estrutura típica para isso seria:

  • Módulo UI, responsável apenas pela representação visual.
  • Módulo de Lógica/Modelo, contendo apenas a lógica de negócios. Um exemplo disso é um hook personalizado.
  • Módulo de Biblioteca, contendo todas as utilidades necessárias para o componente.

Aqui está um pequeno exemplo de demonstração para ajudar a ilustrar esse conceito.

export function ListComponent() {
  // Nosso estado local
  const [list, setList] = useState([]);

  // Handler buscar os dados do server
  const fetchList = async () => {
    try {
      const resp = await fetch("https://www.url.com/list");
      const data = await resp.json();
      setList(data);

    } catch {
      showAlert({ text: "Something went wrong!" });
    }
  };

  // Fazemos a requisição apenas na montagem do componente
  useEffect(() => {
    fetchList();
  }, []);

  // Handler responsável por deletar itens
  const handleDeleteItem = (id) => {
    return () => {
      try {
        fetch(`https://www.url.com/list/${id}`, {
          method: "DELETE"
        });
        setList((prev) => prev.filter((x) => x.id !== id));

      } catch {
        showAlert({ text: "Something went wrong!" });
      }
    };
  };

  // Renderizado os dados
  return (
    <div className="list-component">
      {list.map(({ id, name }) => (
        <div key={id} className="list-component__item>">
          {/* We want to trim long name with ellipsis */}
          {name.slice(0, 30) + (name.length > 30 ? "..." : "")}

          <div onClick={handleDeleteItem(id)} className="list-component__icon">
            <DeleteIcon />
          </div>
        </div>
      ))}
    </div>
  );
}

Devemos começar escrevendo nossos utilitários que serão usados nos módulos de modelo e UI.

export async function getList(onSuccess) {
  try {
    const resp = await fetch("https://www.url.com/list");
    const data = await resp.json();
    onSuccess(data)

  } catch {
    showAlert({ text: "Something went wrong!" });
  }
}

export async function deleteListItem(id, onSuccess) {
  try {
    fetch(`https://www.url.com/list/${id}`, {
      method: "DELETE"
    });
    onSuccess()

  } catch {
    showAlert({ text: "Something went wrong!" });
  }
}

export function trimName(name) {
  return name.slice(0, 30) + (name.lenght > 30 ? '...' : '')
}

Agora precisamos implementar nossa lógica de negócios. Será apenas um hook personalizado retornando as coisas que precisamos.

export function useList() {
  const [list, setList] = useState([]);

  const handleDeleteItem = useCallback((id) => {
    return () => {
      deleteListItem(id, () => {
        setList((prev) => prev.filter((x) => x.id !== id));
      })
    };
  }, []);

  useEffect(() => {
    getList(setList);
  }, []);

  return useMemo(
    () => ({
      list,
      handleDeleteItem
    }),

    [list, handleDeleteItem]
  );
}

O último passo é escrever nosso módulo UI e, em seguida, combiná-lo com todos os outros módulos.

export function ListComponentItem({ name, onDelete }) {
  return (
    <div className="list-component__item>">
      {trimName(name)}

      <div onClick={onDelete} className="list-component__icon">
        <DeleteIcon />
      </div>
    </div>
  );
}

export function ListComponent() {
  const { list, handleDeleteItem } = useList();

  return (
    <div className="list-component">
      {list.map(({ id, name }) => (
        <ListComponentItem
          key={id}
          name={name}
          onDelete={handleDeleteItem(id)}
        />
      ))}
    </div>
  );
}

Use objetos map em vez de condições

Se você precisa exibir vários elementos dependendo da variável, pode implementar essa dica. Usar essa estratégia simples torna os componentes mais declarativos e simplifica a compreensão do código. Além disso, torna mais fácil estender ainda mais a funcionalidade.

Problema

function Account({type}) {
  let Component = UsualAccount

  if (type === 'vip') {
    Component = VipAccount
  }

  if (type === 'moderator') {
    Component = ModeratorAccount
  }

  if (type === 'admin') {
    Component = AdminAccount
  }

  return (
    <div className='account'>
      <Component />
      <AccountStatistics />
    </div>
  )
}

Solução

const ACCOUNTS_MAP = {
  'vip': VipAccount,
  'usual': UsualAccount,
  'admin': AdminAccount,
  'moderator': ModeratorAccount,
}

function Account({type}) {
  const Component = ACCOUNTS_MAP[type]

  return (
    <div className='account'>
      <Component />
      <AccountStatistics />
    </div>
  )
}

Coloque variáveis independentes fora do ciclo de vida do React

A ideia é separar a lógica que não requer métodos do ciclo de vida do componente React do próprio componente. Isso melhora a clareza do código tornando as dependências mais explícitas. Portanto, torna-se muito mais fácil ler e entender os componentes.

Problema

function useItemsList() {
  const defaultItems = [1, 2, 3, 4, 5]
  const [items, setItems] = useState(defaultItems)

  const toggleArrayItem = (arr, val) => {
    return arr.includes(val) ? arr.filter(el => el !== val) : [...arr, val];
  }

  const handleToggleItem = (num) => {
    return () => {
      setItems(toggleArrayItem(items, num))
    }
  }

  return {
    items,
    handleToggleItem,
  }
}

Solução

const DEFAULT_ITEMS = [
  1, 2, 3, 4, 5
]

const toggleArrayItem = (arr, val) => {
  return arr.includes(val) ? arr.filter(el => el !== val) : [...arr, val];
}

function useItemsList() {
  const [items, setItems] = useState(DEFAULT_ITEMS)

  const handleToggleItem = (num) => {
    return () => {
      setItems(toggleArrayItem(items, num))
    }
  }

  return {
    items,
    handleToggleItem,
  }
}
AdsTerra, Junte-se ao AdsTerra