¿Cómo usar React.forwardRef en un componente basado en clases?

112

Estoy tratando de usar React.forwardRef, pero me tropiezo con cómo hacer que funcione en un componente basado en clases (no HOC).

Los ejemplos de documentos usan elementos y componentes funcionales, incluso envolviendo clases en funciones para componentes de orden superior.

Entonces, comenzando con algo como esto en su ref.jsarchivo:

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

y en su lugar definirlo como algo como esto:

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

o

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

solo funciona: /

Además, sé que sé, los árbitros no son la forma de reaccionar. Estoy tratando de usar una biblioteca de lienzo de terceros y me gustaría agregar algunas de sus herramientas en componentes separados, por lo que necesito detectores de eventos, por lo que necesito métodos de ciclo de vida. Puede que más adelante tome una ruta diferente, pero quiero probar esto.

¡Los doctores dicen que es posible!

El reenvío de referencias no se limita a los componentes DOM. También puede reenviar referencias a instancias de componentes de clase.

de la nota en esta sección.

Pero luego pasan a usar HOC en lugar de solo clases.

Han Lazarus
fuente

Respuestas:

108

La idea de usar siempre el mismo accesorio para el refse puede lograr mediante el proxy de exportación de clases con un ayudante.

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}
/>);

Entonces, básicamente, sí, nos vemos obligados a tener un accesorio diferente para reenviar la referencia, pero se puede hacer debajo del hub. Es importante que el público lo use como una referencia normal.

Sr. Br
fuente
4
¿Qué hace si tiene su componente envuelto en un HOC como React-Redux connecto marterial-ui's withStyles?
J. Hesters
¿Cuál es el problema con connecto withStyles? Debe envolver todos los HOC con forwardRefy usar internamente un accesorio "seguro" para pasar una referencia al componente más bajo de la cadena.
Mr Br
¿Alguna idea de cómo probar esto en Enzyme cuando usa setState?
zero_cool
Lo siento, necesitaré un poco más de detalles. No estoy seguro de cuál es el problema exactamente.
Mr Br
2
Estoy obteniendo: Violación invariante: los objetos no son válidos como un hijo de React (encontrado: objeto con claves {$$ typeof, render}). Si pretendía representar una colección de elementos secundarios, utilice una matriz en su lugar.
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()} />
)

Debe usar un nombre de accesorio diferente para reenviar la referencia a una clase. innerRefse usa comúnmente en muchas bibliotecas.

Sebastien Lorber
fuente
en su código beautifulInputElementes más una función que un elemento de reacción, debería ser asíconst beautifulInputElement = <BeautifulInputForwardingRef ref={ref => ref && ref.focus()} />
Olivier Boissé
8

Básicamente, esta es solo una función HOC. Si desea usarlo con clase, puede hacerlo usted mismo y usar accesorios regulares.

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

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

Este patrón se usa, por ejemplo, en styled-componentsy se llama innerRefallí.

Łukasz Wojciechowski
fuente
3
Usar un accesorio con un nombre diferente como innerRefes perder completamente el punto. El objetivo es la transparencia total: debería poder tratar a <FancyButton>como si fuera un elemento DOM regular, pero usando el enfoque de componentes con estilo, debo recordar que este componente usa un prop con nombre arbitrario para referencias en lugar de solo ref.
user128216
2
En cualquier caso, styled-components tiene la intención de eliminar el soporte ainnerRef favor de pasar la referencia al niño que usa React.forwardRef, que es lo que el OP está tratando de lograr.
user128216
5

Esto se puede lograr con un componente de orden superior, si lo desea:

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

Y luego en su archivo de componente:

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

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

export default withForwardedRef(Boop)

Hice el trabajo por adelantado con pruebas y publiqué un paquete para esto react-with-forwarded-ref: https://www.npmjs.com/package/react-with-fordered-ref

rpearce
fuente
3

En caso de que necesite reutilizar esto en muchos componentes diferentes, puede exportar esta capacidad a 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
fuente