Guia Completo de Render Props em React: Quando Ainda Fazem Sentido Já leu

O Que São Render Props? Render Props é um padrão de design em React que consiste em passar uma função como prop para um componente, que então a chama para renderizar seu conteúdo. Essa função, chamada de "render prop", recebe dados do componente pai e retorna elementos React que serão renderizados. O nome vem justamente dessa prática: você está passando uma prop que é uma função responsável por renderizar algo. A ideia principal é desacoplar a lógica de um componente (como gerenciamento de estado, efeitos colaterais, ou comunicação com APIs) da sua apresentação visual. O componente que possui a lógica não precisa decidir o que renderizar; quem está usando o componente é quem decide. Isso tornou o padrão extremamente popular nos anos 2017-2019, principalmente antes da consolidação do React Hooks. Por Que Render Props Fizeram Sentido? O Contexto Histórico: Antes dos Hooks Antes do React 16.8 (que introduziu os Hooks), a reutilização de lógica entre componentes era um problema real.

O Que São Render Props?

Render Props é um padrão de design em React que consiste em passar uma função como prop para um componente, que então a chama para renderizar seu conteúdo. Essa função, chamada de "render prop", recebe dados do componente pai e retorna elementos React que serão renderizados. O nome vem justamente dessa prática: você está passando uma prop que é uma função responsável por renderizar algo.

A ideia principal é desacoplar a lógica de um componente (como gerenciamento de estado, efeitos colaterais, ou comunicação com APIs) da sua apresentação visual. O componente que possui a lógica não precisa decidir o que renderizar; quem está usando o componente é quem decide. Isso tornou o padrão extremamente popular nos anos 2017-2019, principalmente antes da consolidação do React Hooks.

Por Que Render Props Fizeram Sentido?

O Contexto Histórico: Antes dos Hooks

Antes do React 16.8 (que introduziu os Hooks), a reutilização de lógica entre componentes era um problema real. Higher-Order Components (HOCs) resolviam parte do problema, mas tinham desvantagens como wrapper hell e dificuldade em rastrear props. Render Props ofereceu uma alternativa mais explícita e flexível para compartilhar lógica sem criar camadas extras de componentes.

Imagine um cenário comum: você tinha um componente que gerenciava o estado de um mouse, buscava dados de uma API, ou controlava permissões de um usuário. Sem Hooks, a única forma elegante de reutilizar essa lógica em vários componentes visuais diferentes era através de Render Props ou HOCs. Render Props venceu em muitos casos porque era mais fácil de debugar e entender o fluxo de dados.

Render Props na Prática: Exemplos Reais

Exemplo 1: Componente de Posição do Mouse

Vamos criar um componente que rastreia a posição do mouse e passa essa informação para qualquer componente que queira usá-la:

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

class MousePosition extends React.Component {
  constructor(props) {
    super(props);
    this.state = { x: 0, y: 0 };
  }

  componentDidMount() {
    window.addEventListener('mousemove', this.handleMouseMove);
  }

  componentWillUnmount() {
    window.removeEventListener('mousemove', this.handleMouseMove);
  }

  handleMouseMove = (event) => {
    this.setState({ x: event.clientX, y: event.clientY });
  };

  render() {
    return this.props.render(this.state);
  }
}

// Usando o componente
function App() {
  return (
    <MousePosition
      render={({ x, y }) => (
        <div>
          <p>Posição do mouse: ({x}, {y})</p>
          <div
            style={{
              width: '50px',
              height: '50px',
              position: 'absolute',
              left: x,
              top: y,
              backgroundColor: 'red',
              borderRadius: '50%',
            }}
          />
        </div>
      )}
    />
  );
}

export default App;

Repare que MousePosition não sabe o que renderizar. Quem determina a apresentação é quem o utiliza, através da prop render. Essa separação clara entre lógica e apresentação era a grande vantagem do padrão.

Exemplo 2: Componente de Busca de Dados

Vamos criar um componente que busca dados de uma API e deixa a renderização para o consumidor:

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

class DataFetcher extends React.Component {
  constructor(props) {
    super(props);
    this.state = { data: null, loading: true, error: null };
  }

  componentDidMount() {
    fetch(this.props.url)
      .then((response) => response.json())
      .then((data) => this.setState({ data, loading: false }))
      .catch((error) => this.setState({ error, loading: false }));
  }

  render() {
    return this.props.render(this.state);
  }
}

// Usando o componente
function PostsList() {
  return (
    <DataFetcher
      url="https://jsonplaceholder.typicode.com/posts?_limit=5"
      render={({ data, loading, error }) => {
        if (loading) return <p>Carregando...</p>;
        if (error) return <p>Erro: {error.message}</p>;
        return (
          <ul>
            {data.map((post) => (
              <li key={post.id}>{post.title}</li>
            ))}
          </ul>
        );
      }}
    />
  );
}

export default PostsList;

Novamente, o componente DataFetcher é um recipiente puro de lógica. A decisão sobre como exibir os dados fica com o consumidor.

Render Props Versus Alternativas Modernas

Por Que Hooks Tornaram Render Props Menos Relevante

O React Hooks, introduzido em 2019, ofereceu uma forma muito mais simples de reutilizar lógica. Em vez de criar um componente wrapper, você pode criar um custom hook. Compare:

// Com Render Props
<MousePosition
  render={({ x, y }) => <div>X: {x}, Y: {y}</div>}
/>

// Com Hooks (alternativa moderna)
function MyComponent() {
  const { x, y } = useMousePosition();
  return <div>X: {x}, Y: {y}</div>;
}

O código com Hooks é mais legível, menos aninhado (evita o "callback hell") e mais intuitivo. Um custom hook useMousePosition() faz a mesma coisa que o componente MousePosition com Render Props, mas de forma mais direta.

Quando Render Props Ainda Fazem Sentido

Apesar da popularidade dos Hooks, existem cenários específicos onde Render Props ainda são a escolha correta:

  1. Componentes de terceiros baseados em classe: Se você está integrando uma biblioteca que usa componentes de classe e oferece Render Props, não há razão para refatorar se está funcionando bem.

  2. Lógica vinculada ao ciclo de vida visual: Alguns padrões de UI, como tooltips ou popovers que precisam posicionar-se dinamicamente com base em refs, podem ser mais naturais com Render Props do que com Hooks customizados.

  3. Biblioteca própria com suporte a múltiplas versões: Se sua biblioteca precisa suportar versões antigas do React (antes do 16.8), Render Props é a opção viável.

  4. Composição dinâmica complexa: Em casos onde a lógica e apresentação precisam estar fortemente sincronizadas e alternar entre componentes é necessário, Render Props pode ser mais explícito que Hooks.

// Um caso onde Render Props ainda é elegante:
// Um componente de abas/tabs onde a lógica de qual aba está ativa
// precisa coordenar a renderização de múltiplos elementos

class Tabs extends React.Component {
  constructor(props) {
    super(props);
    this.state = { activeTab: 0 };
  }

  render() {
    const { activeTab } = this.state;
    const { tabs } = this.props;

    return (
      <div>
        <div className="tabs-header">
          {tabs.map((tab, index) => (
            <button
              key={index}
              onClick={() => this.setState({ activeTab: index })}
              style={{
                fontWeight: activeTab === index ? 'bold' : 'normal',
              }}
            >
              {tab.label}
            </button>
          ))}
        </div>
        <div className="tabs-content">
          {this.props.render({ activeTab, tabs })}
        </div>
      </div>
    );
  }
}

// Uso:
function App() {
  const tabsConfig = [
    { label: 'Home', content: 'Conteúdo da home' },
    { label: 'Sobre', content: 'Conteúdo sobre' },
  ];

  return (
    <Tabs
      tabs={tabsConfig}
      render={({ activeTab, tabs }) => (
        <div>{tabs[activeTab].content}</div>
      )}
    />
  );
}

Neste exemplo, a coordenação entre o header (botões) e o conteúdo é forte, e o Render Props deixa explícito que o consumidor tem controle total sobre como o conteúdo ativo é exibido.

O Padrão "Children as a Function"

Uma variação de Render Props que ainda é bastante usada é passar a função como children em vez de uma prop nomeada:

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

function WindowSize({ children }) {
  const [size, setSize] = useState({ width: 0, height: 0 });

  useEffect(() => {
    const handleResize = () => {
      setSize({ width: window.innerWidth, height: window.innerHeight });
    };

    window.addEventListener('resize', handleResize);
    handleResize(); // Chamada inicial

    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return children(size);
}

// Uso:
function App() {
  return (
    <WindowSize>
      {({ width, height }) => (
        <div>
          <p>Largura: {width}px</p>
          <p>Altura: {height}px</p>
        </div>
      )}
    </WindowSize>
  );
}

export default App;

Essa abordagem é particularmente legível quando você tem uma única função como children. Bibliotecas populares como react-router ainda usam variações desse padrão em certas APIs.

Conclusão

Render Props foi um padrão revolucionário que resolveu um problema real na era pré-Hooks do React. Ele forneceu uma forma explícita e flexível de separar lógica de apresentação, evitando o wrapper hell dos HOCs. No entanto, com a chegada dos Hooks, a maioria dos casos de uso foi simplificada drasticamente.

A lição principal é: Render Props não desapareceram, apenas perderam relevância como a ferramenta padrão. Eles ainda são apropriados em contextos específicos, principalmente quando você herda código que já os usa ou quando está criando bibliotecas que precisam oferecer máxima flexibilidade compositiva. A decisão entre Hooks e Render Props deve ser pragmática, considerando o contexto, a legibilidade do código e o público-alvo da sua solução.

Finalmente, entender Render Props é importante não apenas para manutenção de código legado, mas porque o padrão em si — a ideia de deixar a renderização nas mãos do consumidor — continua sendo uma poderosa estratégia de design que transcende frameworks e linguagens.

Referências


Artigos relacionados