web-dev-qa-db-fra.com

Comment puis-je attacher à la référence d'un composant sans état dans React?

Je cherche à créer un composant sans état dont l'entrée peut être validée par l'élément parent.

Dans mon exemple ci-dessous, je rencontre un problème où la référence d'entrée n'est jamais affectée à la propriété privée _emailAddress du parent.

Lorsque handleSubmit est appelé, this._emailAddress est undefined. Y a-t-il quelque chose qui me manque ou y a-t-il une meilleure façon de le faire?

interface FormTestState {
    errors: string;
}

class FormTest extends React.Component<void, FormTestState> {
    componentWillMount() {
        this.setState({ errors: '' });
    }

    render(): JSX.Element {
        return (
            <main role='main' className='about_us'>             
                <form onSubmit={this._handleSubmit.bind(this)}>
                    <TextInput 
                        label='email'
                        inputName='txtInput'
                        ariaLabel='email'
                        validation={this.state.errors}
                        ref={r => this._emailAddress = r}
                    />

                    <button type='submit'>submit</button>
                </form>
            </main>
        );
    }

    private _emailAddress: HTMLInputElement;

    private _handleSubmit(event: Event): void {
        event.preventDefault();
        // this._emailAddress is undefined
        if (!Validators.isEmail(this._emailAddress.value)) {
            this.setState({ errors: 'Please enter an email address.' });
        } else {
            this.setState({ errors: 'All Good.' });
        }
    }
}

const TextInput = ({ label, inputName, ariaLabel, validation, ref }: { label: string; inputName: string; ariaLabel: string; validation?: string; ref: (ref: HTMLInputElement) => void }) => (
    <div>
        <label htmlFor='txt_register_first_name'>
            { label }
        </label>

        <input type='text' id={inputName} name={inputName} className='input ' aria-label={ariaLabel} ref={ref} />

        <div className='input_validation'>
            <span>{validation}</span>
        </div>
    </div>
);
14
drewwyatt

Vous ne pouvez pas accéder aux méthodes de type React (comme componentDidMount, componentWillReceiveProps, etc.) sur des composants sans état, y compris refs. Checkout cette discussion sur GH pour la convo complète. 

L'idée de apatride est qu'il n'y a pas d'instance créée pour elle (état). En tant que tel, vous ne pouvez pas attacher une ref, car il n'y a aucun état auquel attacher la référence.

Votre meilleur choix serait de passer un rappel pour le moment où le composant change, puis d'affecter ce texte à l'état du parent. 

Vous pouvez également renoncer au composant sans état et utiliser un composant de classe normal.

Des docs ...

Vous ne pouvez pas utiliser l'attribut ref sur des composants fonctionnels car ils ne possèdent pas d'instances. Vous pouvez toutefois utiliser l'attribut ref à l'intérieur de la fonction de rendu d'un composant fonctionnel.

function CustomTextInput(props) {
  // textInput must be declared here so the ref callback can refer to it
  let textInput = null;

  function handleClick() {
    textInput.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={(input) => { textInput = input; }} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );  
}
30
Brad Bumbalough

Vous pouvez utiliser le hook useRef qui est disponible depuis v16.7.0-alpha.

EDIT: Nous vous encourageons à utiliser Hooks en production à partir de la version 16.8.0!

Les crochets vous permettent de maintenir l'état et de gérer les effets secondaires des composants fonctionnels.

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

En savoir plus dans Documentation de l'API Hooks

3
Ante Gulin

La valeur de votre TextInput n'est rien d'autre qu'un état de votre composant. Ainsi, au lieu de récupérer la valeur actuelle avec une référence (mauvaise idée en général, autant que je sache), vous pouvez récupérer l'état actuel.

En version réduite (sans taper):

class Form extends React.Component {
  constructor() {
    this.state = { _emailAddress: '' };

    this.updateEmailAddress = this.updateEmailAddress.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  updateEmailAddress(e) {
    this.setState({ _emailAddress: e.target.value });
  }

  handleSubmit() {
    console.log(this.state._emailAddress);
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          value={this.state._emailAddress}
          onChange={this.updateEmailAddress}
        />
      </form>
    );
  }
}
1
Ole Bläsing