Réagir: "ceci" n'est pas défini dans une fonction de composant

152
class PlayerControls extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      loopActive: false,
      shuffleActive: false,
    }
  }

  render() {
    var shuffleClassName = this.state.toggleActive ? "player-control-icon active" : "player-control-icon"

    return (
      <div className="player-controls">
        <FontAwesome
          className="player-control-icon"
          name='refresh'
          onClick={this.onToggleLoop}
          spin={this.state.loopActive}
        />
        <FontAwesome
          className={shuffleClassName}
          name='random'
          onClick={this.onToggleShuffle}
        />
      </div>
    );
  }

  onToggleLoop(event) {
    // "this is undefined??" <--- here
    this.setState({loopActive: !this.state.loopActive})
    this.props.onToggleLoop()
  }

Je veux mettre à jour l' loopActiveétat sur bascule, mais l' thisobjet n'est pas défini dans le gestionnaire. Selon la doc du tutoriel, je thisdevrais me référer au composant. Est-ce que je manque quelque chose?

Maximus S
la source

Réponses:

211

ES6 React.Component ne lie pas automatiquement les méthodes à lui-même. Vous devez les lier vous-même dans le constructeur. Comme ça:

constructor (props){
  super(props);

  this.state = {
      loopActive: false,
      shuffleActive: false,
    };

  this.onToggleLoop = this.onToggleLoop.bind(this);

}
Ivan
la source
23
si vous changez votre propriété onClick () => this.onToggleLoopaprès avoir déplacé la fonction onToggleLoop dans votre classe react, cela fonctionnera également.
Sam
71
Devez-vous vraiment lier chaque méthode de chaque classe de réaction? N'est-ce pas un peu fou?
Alex L
6
@AlexL Il existe des moyens de le faire sans lier explicitement les méthodes. si vous utilisez babel, il est possible de déclarer chaque méthode sur le composant React comme des fonctions fléchées. Il y a des exemples ici: babeljs.io/blog/2015/06/07/react-on-es6-plus
Ivan
7
Mais pourquoi est-ce thisindéfini en premier lieu? Je sais que thisJavascript dépend de la manière dont la fonction est appelée, mais que se passe-t-il ici?
maverick
1
mais comment puis-je faire cela si la fonction n'est définie qu'après le constructeur? J'obtiens un "Cannot read property 'bind' of undefined" si j'essaie de faire cela dans le constructeur même si ma fonction est définie sur la classe :(
rex
87

Il y a plusieurs façons.

La première consiste à ajouter this.onToggleLoop = this.onToggleLoop.bind(this);le constructeur.

Un autre est les fonctions fléchées onToggleLoop = (event) => {...}.

Et puis il y a onClick={this.onToggleLoop.bind(this)}.

J. Mark Stevens
la source
1
Pourquoi onToogleLoop = () => {} fonctionne-t-il? J'ai eu le même problème et je l'ai lié dans mon constructeur mais cela n'a pas fonctionné ... et maintenant j'ai vu votre message et j'ai remplacé ma méthode par une syntaxe de fonction de flèche et cela fonctionne. Pouvez-vous me l'expliquer?
Guchelkaben
5
de developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ; Une fonction de flèche ne crée pas la sienne propre, la valeur this du contexte d'exécution englobant est utilisée.
J.Mark Stevens
1
Notez que la liaison en ligne dans le onClickrenverra une nouvelle fonction à chaque rendu et donc il semble qu'une nouvelle valeur a été passée pour le prop, jouant avec shouldComponentUpdatedans PureComponents.
Ninjakannon
25

Écrivez votre fonction de cette façon:

onToggleLoop = (event) => {
    this.setState({loopActive: !this.state.loopActive})
    this.props.onToggleLoop()
}

Fonctions de la grosse flèche

la liaison pour le mot-clé this est la même à l'extérieur et à l'intérieur de la fonction grosse flèche. Ceci est différent des fonctions déclarées avec function, qui peuvent lier ceci à un autre objet lors de l'appel. Le maintien de cette liaison est très pratique pour des opérations comme le mappage: this.items.map (x => this.doSomethingWith (x)).

ShaTin
la source
Si je fais ça, je reçois ReferenceError: fields are not currently supported.
Pavel Komarov
Cela fonctionne si à l'intérieur du constructeur je dis this.func = () => {...}, mais je considère cela comme un peu ridicule et je veux éviter cela si possible.
Pavel Komarov
Tellement terrible que vous ne pouvez pas utiliser la syntaxe de classe normale dans React!
Kokodoko
11

J'ai rencontré une liaison similaire dans une fonction de rendu et j'ai fini par passer le contexte de thisde la manière suivante:

{someList.map(function(listItem) {
  // your code
}, this)}

J'ai également utilisé:

{someList.map((listItem, index) =>
    <div onClick={this.someFunction.bind(this, listItem)} />
)}
duhaime
la source
C'est beaucoup de fonctions inutiles que vous créez là-bas, à chaque fois que la liste est rendue ...
TJ Crowder
@TJCrowder Oui, c'est vrai, ces fonctions sont créées à nouveau à chaque appel de rendu. Il est préférable de créer les fonctions en tant que méthodes de classe et de les lier une fois à la classe, mais pour les débutants, la liaison de contexte manuelle peut être utile
duhaime
2

Vous devriez remarquer que cela thisdépend de la façon dont la fonction est appelée, c'est-à-dire: lorsqu'une fonction est appelée en tant que méthode d'un objet, elle thisest définie sur l'objet sur lequel la méthode est appelée.

thisest accessible dans le contexte JSX en tant qu'objet composant, vous pouvez donc appeler la méthode souhaitée en ligne en tant que thisméthode.

Si vous passez simplement une référence à une fonction / méthode, il semble que react l'appellera en tant que fonction indépendante.

onClick={this.onToggleLoop} // Here you just passing reference, React will invoke it as independent function and this will be undefined

onClick={()=>this.onToggleLoop()} // Here you invoking your desired function as method of this, and this in that function will be set to object from that function is called ie: your component object
Jakub Kutrzeba
la source
1
Bien, vous pouvez même utiliser la première ligne c'est à dire à onClick={this.onToggleLoop}condition que dans votre classe de composant vous ayez défini un champ (propriété)onToggleLoop = () => /*body using 'this'*/
gvlax
1

Si vous utilisez babel, vous liez 'this' à l'aide de l'opérateur de liaison ES7 https://babeljs.io/docs/en/babel-plugin-transform-function-bind#auto-self-binding

export default class SignupPage extends React.Component {
  constructor(props) {
    super(props);
  }

  handleSubmit(e) {
    e.preventDefault(); 

    const data = { 
      email: this.refs.email.value,
    } 
  }

  render() {

    const {errors} = this.props;

    return (
      <div className="view-container registrations new">
        <main>
          <form id="sign_up_form" onSubmit={::this.handleSubmit}>
            <div className="field">
              <input ref="email" id="user_email" type="email" placeholder="Email"  />
            </div>
            <div className="field">
              <input ref="password" id="user_password" type="new-password" placeholder="Password"  />
            </div>
            <button type="submit">Sign up</button>
          </form>
        </main>
      </div>
    )
  }

}
Henry Jacob
la source
0

Si vous appelez votre méthode créée dans les méthodes de cycle de vie telles que componentDidMount ..., vous ne pouvez utiliser que la fonction this.onToggleLoop = this.onToogleLoop.bind(this)et la fonction de la grosse flèche onToggleLoop = (event) => {...}.

L'approche normale de la déclaration d'une fonction dans le constructeur ne fonctionnera pas car les méthodes de cycle de vie sont appelées plus tôt.

Guchelkaben
la source
0

dans mon cas, c'était la solution = () => {}

methodName = (params) => {
//your code here with this.something
}
Alex
la source
0

Dans mon cas, pour un composant sans état qui a reçu la référence avec forwardRef, j'ai dû faire ce qu'il est dit ici https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd

À partir de là (onClick n'a pas accès à l'équivalent de 'this')

const Com = forwardRef((props, ref) => {
  return <input ref={ref} onClick={() => {console.log(ref.current} } />
})

Pour cela (ça marche)

const useCombinedRefs = (...refs) => {
  const targetRef = React.useRef()

  useEffect(() => {
    refs.forEach(ref => {
      if (!ref) return

      if (typeof ref === 'function') ref(targetRef.current)
      else ref.current = targetRef.current
    })
  }, [refs])

  return targetRef
}

const Com = forwardRef((props, ref) => {
  const innerRef = useRef()
  const combinedRef = useCombinedRefs(ref, innerRef)

  return <input ref={combinedRef } onClick={() => {console.log(combinedRef .current} } />
})
GWorking
la source