We want to hear from you!Take our 2021 Community Survey!
Este site não é mais atualizado.Vá para pt-br.react.dev

Render Props

These docs are old and won’t be updated. Go to react.dev for the new React docs.

Render props are used in modern React, but aren’t very common.
For many cases, they have been replaced by custom Hooks.

O termo “render prop” se refere a uma técnica de compartilhar código entre componentes React passando uma prop cujo valor é uma função.

Um componente com uma render prop recebe uma função que retorna um elemento React e a invoca no momento de renderização, não sendo necessário para o componente implementar uma lógica própria.

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

Bibliotecas que usam render props incluem React Router, Downshift e Formik.

Nesse documento vamos discutir por que render props são úteis e como escrevê-las.

Uso de Render Props para Características Transversais

Componentes são as principais unidades de reuso de código em React, mas nem sempre é fácil compartilhar o estado ou comportamento que um componente encapsula para outros componentes utilizarem esses valores.

Por exemplo, o componente abaixo captura a posição do mouse em um aplicativo web:

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

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

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        <h1>Move the mouse around!</h1>
        <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}

Enquanto o cursor se move pela tela, o componente mostra suas coordenadas (x, y) em um <p>

A questão é: Como podemos reutilizar esse comportamento em outro componente? Em outras palavras, se outro componente precisar saber a posição do cursor, podemos encapsular esse comportamento de forma que seja fácil compartilhá-lo com outros componentes?

Lembrando que componentes são a unidade básica de reuso de código em React, vamos tentar refatorar esse código para usar o componente <Mouse>, ele encapsula o comportamento que precisamos reutilizar.

// O componente <Mouse> encapsula o comportamento que precisamos...
class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

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

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>

        {/* ...mas como renderizar algo diferente de um <p>? */}
        <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <>
        <h1>Move the mouse around!</h1>
        <Mouse />
      </>
    );
  }
}

Agora o componente <Mouse> encapsula todos os comportamentos associados aos eventos mousemove e guarda a posição (x, y) do cursor, mas ainda não é completamente reutilizável.

Por exemplo, suponha que temos o componente <Cat> que renderiza a image de um gato seguindo o mouse na tela. Poderíamos usar uma prop <Cat mouse={{ x, y }}> que passaria as coordenadas do mouse para o componente de forma que este saberia onde posicionar a imagem na tela.

Inicialmente, você poderia tentar renderizar <Cat> dentro do método render do <Mouse>, assim:

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

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

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

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>

        {/*
          Poderíamos simplesmente trocar o <p> por um <Cat> ... mas assim
          teríamos que criar um componente <MouseWithSomethingElse>
          separado toda vez que precisarmos usá-lo, então <MouseWithCat>
          não é muito reutilizável ainda.
        */}
        <Cat mouse={this.state} />
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <MouseWithCat />
      </div>
    );
  }
}

Essa abordagem funciona para o nosso caso em específico, mas ainda não atingimos o objetivo de encapsular o comportamento de uma maneira completamente reutilizável. Agora, toda vez que precisarmos da posição do mouse para outro caso, teremos que criar um novo componente (ou seja, outro <MouseWithCat>) que renderiza algo especificamente para esse caso.

Aqui é onde a render prop se encaixa: Ao invés de escrever um componente <Cat> dentro de <Mouse>, e mudar diretamente a saída renderizada, podemos passar uma função como prop para <Mouse>, que vai chamá-la para determinar o que renderizar dinamicamente- essa é a render prop.

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

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

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

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>

        {/*
          No lugar de fornecer uma representação estática do que <Mouse> deve
          renderizar, use a `render` prop para determinar o que renderizar
          dinamicamente.
        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

Agora, no lugar de clonar o componente <Mouse> e escrever o método render para resolver um caso específico, nós passamos uma render prop que <Mouse> pode usar para determinar o que ele renderiza dinamicamente.

Portanto, uma render prop é uma função passada nas props que um componente utiliza para determinar o que renderizar.

Essa técnica torna o comportamento que precisamos compartilhar extremamente portátil. Para acessar esse comportamento, basta renderizar um <Mouse> com uma render prop que dirá o que renderizar com o (x, y) atual do cursor.

Algo interessante para notar sobre render props é que você pode implementar a maioria dos higher-order components (HOC) utilizando um componente qualquer com uma render prop. Por exemplo, se você preferir ter um HOC withMouse no lugar de um componente <Mouse>, você poderia simplesmente criar um utilizando o <Mouse> com uma render prop.

// Se você realmente quer usar HOC por alguma razão, você
// pode facilmente criar uma usando um componente qualquer
// com uma render prop!
function withMouse(Component) {
  return class extends React.Component {
    render() {
      return (
        <Mouse render={mouse => (
          <Component {...this.props} mouse={mouse} />
        )}/>
      );
    }
  }
}

Então, utilizar uma render prop torna possível qualquer um dos padrões citados.

Usando outras Props além de render

É importante lembrar que mesmo o nome padrão sendo “render props”, não quer dizer que você deve ter uma prop chamada render para usar esse padrão. Na verdade, qualquer prop que é uma função usada por um componente para determinar o que renderizar é uma “render prop”.

Embora os exemplos acima usem a palavra render, poderíamos usar a prop children!

<Mouse children={mouse => (
  <p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>

Lembre-se, a prop children não precisar estar nomeada na lista de “atributos” do seu elemento JSX. Ao invés disso, você pode inserí-la dentro do seu elemento!

<Mouse>
  {mouse => (
    <p>The mouse position is {mouse.x}, {mouse.y}</p>
  )}
</Mouse>

Você pode ver essa técnica sendo usada na API react-motion.

Dado que essa é uma técnica um pouco incomum, quando você estiver criando uma API como essa, você provavelmente vai querer definir nos seus propTypes que children deve ser uma função.

Mouse.propTypes = {
  children: PropTypes.func.isRequired
};

Avisos

Tenha cuidado ao utilizar Render Props num React.PureComponent

Usar uma render prop pode anular a vantagem de utilizar React.PureComponent se você criar uma função dentro de um método render. Isso se deve à comparação superficial de prop sempre retornar false para novas props, e, nesse caso, cada render vai gerar um novo valor para a render prop.

Por exemplo, continuando com o componente <Mouse> acima, se Mouse estendesse React.PureComponent no lugar de React.Component, nosso exemplo seria assim:

class Mouse extends React.PureComponent {
  // Mesma implementação de antes...
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>

        {/*
          Isso é ruim! O valor da prop `render` vai ser
          diferente para cara render.
        */}
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

Nesse exemplo, cada vez que <MouseTracker> renderiza, ele gera uma nova função como valor da prop <Mouse render>, anulando assim o efeito de <Mouse> estender React.PureComponent!

Para contornar esse problema, você pode definir a prop como um método de instância:

class MouseTracker extends React.Component {
  // Definindo como um método de instância, `this.renderTheCat`
  // sempre se refere a *mesma* função quando chamamos na
  // renderização
  renderTheCat(mouse) {
    return <Cat mouse={mouse} />;
  }

  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={this.renderTheCat} />
      </div>
    );
  }
}

Nos casos onde você não pode definir a prop estaticamente (por exemplo, quando você precisa esconder as props e o estado do componente), <Mouse> deveria estender React.Component.

Esta página é útil?Edite esta página