Como usar React.forwardRef em um componente baseado em classe?

111

Estou tentando usar React.forwardRef, mas estou tropeçando em como fazê-lo funcionar em um componente baseado em classe (não HOC).

Os exemplos de documentos usam elementos e componentes funcionais, até mesmo agrupando classes em funções para componentes de ordem superior.

Então, começando com algo parecido com isso em seu ref.jsarquivo:

const TextInput = React.forwardRef(
    (props, ref) => (<input type="text" placeholder="Hello World" ref={ref} />)
);

e, em vez disso, defini-lo como algo assim:

class TextInput extends React.Component {
  render() {
    let { props, ref } = React.forwardRef((props, ref) => ({ props, ref }));
    return <input type="text" placeholder="Hello World" ref={ref} />;
  }
}

ou

class TextInput extends React.Component {
  render() { 
    return (
      React.forwardRef((props, ref) => (<input type="text" placeholder="Hello World" ref={ref} />))
    );
  }
}

só trabalhando: /

Além disso, eu sei, eu sei, os árbitros não são a maneira de reagir. Estou tentando usar uma biblioteca de telas de terceiros e gostaria de adicionar algumas de suas ferramentas em componentes separados, portanto, preciso de ouvintes de eventos, portanto, preciso de métodos de ciclo de vida. Pode haver um caminho diferente mais tarde, mas quero tentar isso.

Os documentos dizem que é possível!

O encaminhamento de referência não se limita a componentes DOM. Você pode encaminhar refs para instâncias de componentes de classe também.

da nota nesta seção.

Mas então eles passam a usar HOCs em vez de apenas classes.

Han Lazarus
fonte

Respostas:

107

A ideia de sempre usar o mesmo suporte para o refpode ser alcançada através da proxy da exportação de classe com um auxiliar.

class ElemComponent extends Component {
  render() {
    return (
      <div ref={this.props.innerRef}>
        Div has ref
      </div>
    )
  }
}

export default React.forwardRef((props, ref) => <ElemComponent 
  innerRef={ref} {...props}
/>);

Então, basicamente, sim, somos forçados a ter um suporte diferente para encaminhar o ref, mas isso pode ser feito sob o cubo. É importante que o público o use como referência normal.

Sr. Br
fonte
4
O que você faz se tiver seu componente empacotado em um HOC como o React-Redux connectou o marterial-ui withStyles?
J. Hesters de
Qual é o problema com connectou withStyles? Você deve envolver todos os HOCs com forwardRefe usar internamente o suporte "seguro" para passar um ref para o componente mais baixo da cadeia.
Sr. Br
Alguma ideia de como testar isso no Enzyme ao usar setState?
zero_cool
Desculpe, vou precisar de mais alguns detalhes. Não tenho certeza de qual é o problema exatamente.
Sr. Br
2
Estou recebendo: Violação invariável: Objetos não são válidos como um filho React (encontrado: objeto com chaves {$$ typeof, render}). Se você pretendia renderizar uma coleção de filhos, use um array.
Brian Loughnane
9
class BeautifulInput extends React.Component {
  const { innerRef, ...props } = this.props;
  render() (
    return (
      <div style={{backgroundColor: "blue"}}>
        <input ref={innerRef} {...props} />
      </div>
    )
  )
}

const BeautifulInputForwardingRef = React.forwardRef((props, ref) => (
  <BeautifulInput {...props} innerRef={ref}/>
));

const App = () => (
  <BeautifulInputForwardingRef ref={ref => ref && ref.focus()} />
)

Você precisa usar um nome de prop diferente para encaminhar o ref para uma classe. innerRefé comumente usado em muitas bibliotecas.

Sebastien Lorber
fonte
em seu código beautifulInputElementé mais uma função do que um Elemento de const beautifulInputElement = <BeautifulInputForwardingRef ref={ref => ref && ref.focus()} />
reação
8

Basicamente, esta é apenas uma função HOC. Se você quiser usá-lo com a aula, pode fazer isso sozinho e usar acessórios regulares.

class TextInput extends React.Component {
    render() {
        <input ref={this.props.forwardRef} />
    }
}

const ref = React.createRef();
<TextInput forwardRef={ref} />

Este padrão é usado por exemplo em styled-componentse é chamado innerReflá.

Łukasz Wojciechowski
fonte
3
Usar um adereço com um nome diferente innerRefestá completamente errado. O objetivo é a transparência total: devo ser capaz de tratar a <FancyButton>como se fosse um elemento DOM regular, mas usando a abordagem de componentes estilizados, preciso lembrar que esse componente usa um prop nomeado arbitrariamente para refs em vez de apenas ref.
user128216
2
Em qualquer caso, os componentes estilizados pretendem abandonar o suporte parainnerRef em favor de passar o ref para a criança usando React.forwardRef, que é o que o OP está tentando realizar.
user128216
5

Isso pode ser feito com um componente de ordem superior, se desejar:

import React, { forwardRef } from 'react'

const withForwardedRef = Comp => {
  const handle = (props, ref) =>
    <Comp {...props} forwardedRef={ref} />

  const name = Comp.displayName || Comp.name
  handle.displayName = `withForwardedRef(${name})`

  return forwardRef(handle)
}

export default withForwardedRef

E então em seu arquivo de componente:

class Boop extends React.Component {
  render() {
    const { forwardedRef } = this.props

    return (
      <div ref={forwardedRef} />
    )
  }
}

export default withForwardedRef(Boop)

Fiz o trabalho inicial com testes e publiquei um pacote para isso react-with-forwarded-ref: https://www.npmjs.com/package/react-with-forwarded-ref

rpearce
fonte
3

Caso você precise reutilizar isso em muitos componentes diferentes, você pode exportar essa capacidade para algo como withForwardingRef

const withForwardingRef = <Props extends {[_: string]: any}>(BaseComponent: React.ReactType<Props>) =>
    React.forwardRef((props, ref) => <BaseComponent {...props} forwardedRef={ref} />);

export default withForwardingRef;

uso:

const Comp = ({forwardedRef}) => (
 <input ref={forwardedRef} />
)
const EnhanceComponent = withForwardingRef<Props>(Comp);  // Now Comp has a prop called forwardedRef
orepor
fonte