Em poucas palavras, o HOC é uma técnica em que uma função recebe um componente em seu argumento e esta função contém uma lógica que afeta o componente passado por parâmetro, para então renderizá-lo.
O HOC nos permite encapsular uma mesma lógica a ser utilizada em múltiplos componentes sem que precisássemos replicar a lógica em todos eles. Pode ser uma lógica que envolve estilos, permissões, visibilidade de micro-componentes dentro de um componente, além de outros casos de uso.
Show me the code
Imagine que você tenha o seguinte componente:
import React from "react";
export const Me = () => {
const isLoggedIn = useAuth();
async function handleGetMe() {
const response = await fetch('/profile');
const data = await response.json();
return data;
}
if (!isLoggedIn) {
return (
<div>
Você precisa estar logado para ver essas informações.
</div>
);
}
const profile = handleGetMe();
return (
<div>
<h2>{profile.name}</h2>
<p>{profile.job}</p>
</div>
);
};
Como podemos ver acima, esse componente possui uma lógica de permissionamento. Se o usuário estiver logado, é possível ver os seus dados, mas se não estiver, recebe uma mensagem.
No entanto, temos duas observações a fazer:
- Não é tão interessante ter uma lógica assim dentro do componente que tem como objetivo realmente mostrar algo na tela;
- Pode ser que essa lógica precise ser usada em outros componentes e nesse formato teria que replicar a lógica nos outros componentes também.
Com certeza há várias formas de construir uma solução para isso, de forma que pudéssemos reutilizar a lógica em múltiplos componentes sem precisar repetir o contexto.
O HOC é uma dessas soluções. Veja um exemplo de criação e uso desse design pattern.
export default function renderIfAuthenticated(Component: JSX.ElementType) {
return (props) => {
const isLoggedIn = useAuth();
if (!isLoggedIn) {
return (
<div>
Você precisa estar logado para ver essas informações.
</div>
);
}
return <Component {...props} />;
}
}
Agora, nós conseguimos centralizar a lógica dentro de uma função HOC. Em outras palavras, transferimos a responsabilidade para uma única função. Recebemos o componente por parâmetro e o renderizamos apenas se a condição que definimos for verdadeira, nesse caso. E para utilizar podemos fazer assim:
import { renderIfAuthenticated } from "../hocs/renderIfAuthenticated";
export function Me() {
async function handleGetMe() {
const response = await fetch('/profile');
const data = await response.json();
return data;
}
const profile = handleGetMe();
return (
<div>
<h2>{profile.name}</h2>
<p>{profile.job}</p>
</div>
);
}
const MeRenderWithAuth = renderIfAuthenticated(Me);
export default renderIfAuthenticated;
Note que o componente em si, se mantém o mesmo. A linha de código que faz o encapsulamento no HOC é esta:
const MeRenderWithAuth = renderIfAuthenticated(Me);
Assim, podemos reutilizar essa lógica em outros componentes que vão fazer uso da mesma lógica de permissionamento.
Vamos ver outro caso de uso. Por exemplo, ao aplicar os mesmos estilos em alguns componentes e extender estilos que serão diferentes para cada um.
export function renderWithStyles(Component: JSX.Element) {
return (props) => {
const style = {
backgroundColor: "#0B1215",
color: "#F5F5F5",
...props.style,
};
return <Component {...props} style={style} />;
};
}
Aplicando o HOC em múltiplos componentes:
Button
import { renderWithStyles } from "../hocs/renderWithStyles";
export const Button = (props) => (
<button style={{ fontSize: "20px", ...props.style }}>Enviar</button>
);
const StyledButton = renderWithStyles(Button);
export default StyledButton;
Cardboard
import { renderWithStyles } from "../hocs/renderWithStyles";
export const Cardboard = (props) => (
<div style={{ fontSize: "20px", fontFamily: "Roboto", ...props.style }}>Enviar</div>
);
const StyledCardboard = renderWithStyles(Cardboard);
export default StyledCardboard;
Basicamento, o que fizemos acima é o que é feito na biblioteca de estilos CSS-In-JS muito conhecida chamada Styled Components. Veja o exemplo abaixo tirado da documentação:
const Button = styled.button`
color: #BF4F74;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid #BF4F74;
border-radius: 3px;
`;
// Aplicação do HOC
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
O TomatoButton vai herdar os estilos do Button e acrescentar ou substituir propriedades exclusivas para ele.
Considerações finais
Reusabilidade – Os HOC’s nos permitem encapsular funcionalidades que são compartilhadas em vários componentes, promovendo o reuso do código;
Separation of Concerns (SOC) – Ajudam a manter a separação de responsabilidades da aplicação, assim os componentes podem focar no objetivo de uso deles;
Abstração de Código – Como os HOC’s abstraem a lógica em comum de um ou mais componentes, fica mais canalizado e fácil de entender e dar manutenção;
Composição – Podemos combinar vários HOC’s para compor uma funcionalidade complexa dentro dos componentes. Um exemplo de uso de algo assim, mas mais antigo é do MapStateToProps
e do MapDispatchToProps
do React-Redux
:
export default connect(mapStateToProps, mapDispatchToProps)(AppWrapper(MyComponent))
Portanto, fazer uso dos HOC’s tornarão sua aplicação mais fácil de manter e entender, por promover um código limpor e reusável. Assim, suas aplicações React serão mais eficientes.
Mas como todos os padrões de projeto, é preciso pensar no caso de uso, na necessidade e até mesmo na complexidade do projeto para decidir se seria interessante o uso de HOC’s.