Comment utiliser React.forwardRef dans un composant basé sur une classe?

113

J'essaie d'utiliser React.forwardRef, mais je trébuche sur la façon de le faire fonctionner dans un composant basé sur une classe (pas HOC).

Les exemples de documentation utilisent des éléments et des composants fonctionnels, et même des classes d'encapsulation dans des fonctions pour des composants d'ordre supérieur.

Donc, en commençant par quelque chose comme ça dans leur ref.jsfichier:

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

et définissez-le plutôt comme quelque chose comme ceci:

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

fonctionne uniquement: /

De plus, je sais que je sais, les arbitres ne sont pas la manière de réagir. J'essaie d'utiliser une bibliothèque de canevas tierce et j'aimerais ajouter certains de leurs outils dans des composants séparés, j'ai donc besoin d'écouteurs d'événements, j'ai donc besoin de méthodes de cycle de vie. Cela peut emprunter une autre voie plus tard, mais je veux essayer ceci.

La documentation dit que c'est possible!

Le transfert de référence n'est pas limité aux composants DOM. Vous pouvez également transmettre des références aux instances de composant de classe.

à partir de la note de cette section.

Mais ensuite, ils utilisent des HOC au lieu de simples classes.

Han Lazarus
la source

Réponses:

108

L'idée de toujours utiliser le même accessoire pour le refpeut être réalisée par un proxy d'exportation de classe avec un assistant.

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

Donc, fondamentalement, oui, nous sommes obligés d'avoir un accessoire différent pour faire avancer la référence, mais cela peut être fait sous le moyeu. Il est important que le public l'utilise comme une référence normale.

Monsieur Br
la source
4
Que faites-vous si votre composant est enveloppé dans un HOC comme React-Redux connectou marterial-ui withStyles?
J. Hesters
Quel est le problème avec connectou withStyles? Vous devez envelopper tous les HOC avec forwardRefet utiliser en interne un accessoire "sûr" pour passer une référence au composant le plus bas de la chaîne.
Mr Br
Des informations sur la façon de tester cela dans Enzyme lorsque vous utilisez setState?
zero_cool
Désolé, j'ai besoin d'un peu plus de détails. Je ne sais pas exactement quel est le problème.
Mr Br
2
J'obtiens: Violation invariante: les objets ne sont pas valides en tant qu'enfant React (trouvé: objet avec les clés {$$ typeof, render}). Si vous vouliez rendre une collection d'enfants, utilisez plutôt un tableau.
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()} />
)

Vous devez utiliser un nom de prop différent pour transmettre la référence à une classe. innerRefest couramment utilisé dans de nombreuses bibliothèques.

Sébastien Lorber
la source
dans votre code beautifulInputElementest plus une fonction qu'un élément de réaction, ça devrait être comme çaconst beautifulInputElement = <BeautifulInputForwardingRef ref={ref => ref && ref.focus()} />
Olivier Boissé
8

Fondamentalement, ce n'est qu'une fonction HOC. Si vous souhaitez l'utiliser avec la classe, vous pouvez le faire vous-même et utiliser des accessoires réguliers.

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

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

Ce modèle est utilisé par exemple dans styled-componentset il y est appelé innerRef.

Łukasz Wojciechowski
la source
3
L'utilisation d'un accessoire nommé différemment, comme, innerRefmanque complètement le point. Le but est une transparence totale: je devrais être capable de traiter un <FancyButton>comme s'il s'agissait d'un élément DOM normal, mais en utilisant l'approche des composants stylisés, je dois me rappeler que ce composant utilise un accessoire nommé arbitrairement pour les références au lieu de simplement ref.
user128216
2
Dans tous les cas, styled-components a l'intention de supprimer le support pourinnerRef en faveur de la transmission de la référence à l'enfant qui utilise React.forwardRef, ce que l'OP essaie d'accomplir.
user128216
5

Cela peut être accompli avec un composant d'ordre supérieur, si vous le souhaitez:

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

Et puis dans votre fichier composant:

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

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

export default withForwardedRef(Boop)

Je l' ai fait l'avance de travail avec des tests et publié un package pour cela, react-with-forwarded-ref: https://www.npmjs.com/package/react-with-forwarded-ref

rpearce
la source
3

Si vous devez réutiliser cela dans de nombreux composants différents, vous pouvez exporter cette capacité vers quelque chose comme withForwardingRef

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

export default withForwardingRef;

usage:

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