Reaccionar: "esto" no está definido dentro de una función componente

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()
  }

Quiero actualizar el loopActiveestado en alternar, pero el thisobjeto no está definido en el controlador. De acuerdo con el tutorial doc, thisdebería referirme al componente. ¿Me estoy perdiendo de algo?

Maximus S
fuente

Respuestas:

211

ES6 React.Component no se vincula automáticamente con los métodos. Debes vincularlos tú mismo en el constructor. Me gusta esto:

constructor (props){
  super(props);

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

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

}
Ivan
fuente
23
si cambia su propiedad onClick a () => this.onToggleLoopdespués de mover la función onToggleLoop a su clase de reacción, también funcionará.
Sam
71
¿Realmente tienes que vincular cada método de cada clase de reacción? ¿No es un poco loco?
Alex L
66
@AlexL Hay formas de hacerlo sin vincular explícitamente los métodos. si usa babel, es posible declarar todos los métodos en el componente React como funciones de flecha. Aquí hay ejemplos: babeljs.io/blog/2015/06/07/react-on-es6-plus
Ivan
77
Pero, ¿por qué thisno está definido en primer lugar? Sé que thisen Javascript depende de cómo se llama la función, pero ¿qué está pasando aquí?
inconformista
1
pero ¿cómo puedo hacer esto si la función no está definida hasta después del constructor? Obtengo un "No se puede leer la propiedad 'bind' de undefined" si trato de hacer esto en el constructor incluso si mi función está definida en la clase :(
rex
87

Hay un par de maneras.

Una es agregar this.onToggleLoop = this.onToggleLoop.bind(this);el constructor.

Otro son las funciones de flecha onToggleLoop = (event) => {...}.

Y luego está onClick={this.onToggleLoop.bind(this)}.

J. Mark Stevens
fuente
1
¿Por qué funciona onToogleLoop = () => {}? Tengo el mismo problema y lo coloqué en mi constructor, pero no funcionó ... y ahora he visto su publicación y reemplazo mi método con una sintaxis de función de flecha y funciona. Me lo puedes explicar ?
Guchelkaben
55
de developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ; Una función de flecha no crea su propio esto, se utiliza el valor de este del contexto de ejecución adjunto.
J. Mark Stevens
1
Tenga en cuenta que la unión en línea en el onClickdevolverá una nueva función de cada representación y por lo tanto se ve como un nuevo valor ha sido aprobada por la hélice, de jugar con shouldComponentUpdateen el PureComponents.
Ninjakannon
24

Escribe tu función de esta manera:

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

Funciones Fat Arrow

El enlace para la palabra clave es el mismo fuera y dentro de la función de flecha gruesa. Esto es diferente de las funciones declaradas con function, que pueden vincular esto a otro objeto tras la invocación. Mantener el enlace this es muy conveniente para operaciones como el mapeo: this.items.map (x => this.doSomethingWith (x)).

ShaTin
fuente
Si hago eso me sale ReferenceError: fields are not currently supported.
Pavel Komarov
Funciona si dentro del constructor digo this.func = () => {...}, pero lo considero un poco tonto y quiero evitarlo si es posible.
Pavel Komarov
¡Tan terrible que no puedes usar la sintaxis de clase normal en React!
Kokodoko
11

Me encontré con un enlace similar en una función de render y terminé pasando el contexto de thisla siguiente manera:

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

También he usado:

{someList.map((listItem, index) =>
    <div onClick={this.someFunction.bind(this, listItem)} />
)}
duhaime
fuente
Estas son muchas funciones innecesarias que estás creando allí, cada vez que se muestra la lista ...
TJ Crowder
@TJCrowder Sí, es cierto que estas funciones se crean de nuevo cada vez que se llama render. Es mejor crear las funciones como métodos de clase y vincularlas una vez a la clase, pero para principiantes el enlace de contexto manual puede ser útil
duhaime
2

Debe notar que thisdepende de cómo se invoque la función, es decir: cuando se llama a una función como método de un objeto, thisse establece en el objeto al que se llama el método.

thises accesible en el contexto JSX como su objeto componente, por lo que puede llamar al método deseado en línea como thismétodo.

Si solo pasa referencia a la función / método, parece que reaccionar lo invocará como función independiente.

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
fuente
1
Correcto, incluso puede usar la primera línea, es decir, onClick={this.onToggleLoop}siempre que en su clase de componente haya definido un campo (propiedad)onToggleLoop = () => /*body using 'this'*/
gvlax
1

Si está utilizando babel, puede vincular 'esto' con el operador de vinculación 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
fuente
0

Si llama a su método creado en los métodos del ciclo de vida como componentDidMount ... entonces solo puede usar la función this.onToggleLoop = this.onToogleLoop.bind(this)y la flecha de grasa onToggleLoop = (event) => {...}.

El enfoque normal de la declaración de una función en el constructor no funcionará porque los métodos del ciclo de vida se llaman anteriormente.

Guchelkaben
fuente
0

en mi caso esta fue la solución = () => {}

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

En mi caso, para un componente sin estado que recibió la referencia con forwardRef, tuve que hacer lo que se dice aquí https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd

A partir de esto (onClick no tiene acceso al equivalente de 'this')

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

A esto (funciona)

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} } />
})
GTrabajo
fuente