web-dev-qa-db-fra.com

React js onClick ne peut pas transmettre de valeur à la méthode

Je souhaite lire les propriétés de la valeur d'événement OnClick. Mais quand je clique dessus, je vois quelque chose comme ça sur la console:

SyntheticMouseEvent {dispatchConfig: Object, dispatchMarker: ".1.1.0.2.0.0:1", nativeEvent: MouseEvent, type: "click", target

Mon code fonctionne correctement. Quand je cours, je peux voir {column} mais je ne peux pas l'obtenir dans l'événement onClick.

Mon code:

var HeaderRows = React.createClass({
    handleSort:  function(value) {
       console.log(value);
    },
    render: function () {
        var that = this;
        return(
        <tr>
            {this.props.defaultColumns.map(function (column) {
                return (
                    <th value={column} onClick={that.handleSort} >{column}</th>
                );
            })}
            {this.props.externalColumns.map(function (column) {
                // Multi dimension array - 0 is column name
                var externalColumnName = column[0];
                return ( <th>{externalColumnName}</th>
                );
            })}

        </tr>);
    }
});

Comment puis-je transmettre une valeur à l'événement onClick dans React js?

519
user1924375

Moyen facile

Utilisez une fonction de flèche:

_return (
  <th value={column} onClick={() => this.handleSort(column)}>{column}</th>
);
_

Cela créera une nouvelle fonction qui appelle handleSort avec les bons paramètres.

Meilleure façon

Extrayez-le dans un sous-composant. Le problème avec l'utilisation d'une fonction de flèche dans l'appel de rendu est qu'il va créer une nouvelle fonction à chaque fois, ce qui entraîne des ré-rendus inutiles.

Si vous créez un sous-composant, vous pouvez transmettre handler et utiliser props comme arguments, qui ne seront alors restitués que lorsque les props changent (car la référence du handler ne change jamais):

Sous-composant

_class TableHeader extends Component {
  handleClick = () => {
    this.props.onHeaderClick(this.props.value);
  }

  render() {
    return (
      <th onClick={this.handleClick}>
        {this.props.column}
      </th>
    );
  }
}
_

Composante principale

_{this.props.defaultColumns.map((column) => (
  <TableHeader
    value={column}
    onHeaderClick={this.handleSort}
  />
))}
_

Ancien moyen simple (ES5)

Utilisez .bind pour transmettre le paramètre souhaité:

_return (
  <th value={column} onClick={that.handleSort.bind(that, column)}>{column}</th>
);
_
1096
Austin Greco

Il y a de bonnes réponses ici, et je suis d'accord avec @Austin Greco (la deuxième option avec des composants séparés)
Il y a une autre façon que j'aime, currying .
Ce que vous pouvez faire est de créer une fonction qui accepte un paramètre (votre paramètre) et renvoie une autre fonction qui accepte un autre paramètre (l'événement click, dans ce cas). alors vous êtes libre de faire ce que vous voulez.

ES5:

handleChange(param) { // param is the argument you passed to the function
    return function (e) { // e is the event object that returned

    };
}

ES6:

handleChange = param => e => {
    // param is the argument you passed to the function
    // e is the event object that returned
};

Et vous allez l'utiliser de cette façon:

<input 
    type="text" 
    onChange={this.handleChange(someParam)} 
/>

Voici un exemple complet d'une telle utilisation:

const someArr = ["A", "B", "C", "D"];

class App extends React.Component {
  state = {
    valueA: "",
    valueB: "some initial value",
    valueC: "",
    valueD: "blah blah"
  };

  handleChange = param => e => {
    const nextValue = e.target.value;
    this.setState({ ["value" + param]: nextValue });
  };

  render() {
    return (
      <div>
        {someArr.map(obj => {
          return (
            <div>
              <label>
                {`input ${obj}   `}
              </label>
              <input
                type="text"
                value={this.state["value" + obj]}
                onChange={this.handleChange(obj)}
              />
              <br />
              <br />
            </div>
          );
        })}
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Notez que cette approche ne résout pas la création d'une nouvelle instance à chaque rendu.
J'aime cette approche par rapport aux autres gestionnaires intégrés, car celle-ci est plus concise et lisible à mon avis.

Modifier:
Comme suggéré dans les commentaires ci-dessous, vous pouvez mettre en cache/mémoriser le résultat de la fonction.

Voici une implémentation naïve:

let memo = {};

const someArr = ["A", "B", "C", "D"];

class App extends React.Component {
  state = {
    valueA: "",
    valueB: "some initial value",
    valueC: "",
    valueD: "blah blah"
  };

  handleChange = param => {
    const handler = e => {
      const nextValue = e.target.value;
      this.setState({ ["value" + param]: nextValue });
    }
    if (!memo[param]) {
      memo[param] = e => handler(e)
    }
    return memo[param]
  };

  render() {
    return (
      <div>
        {someArr.map(obj => {
          return (
            <div key={obj}>
              <label>
                {`input ${obj}   `}
              </label>
              <input
                type="text"
                value={this.state["value" + obj]}
                onChange={this.handleChange(obj)}
              />
              <br />
              <br />
            </div>
          );
        })}
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root" />
113
Sagiv b.g

De nos jours, avec ES6, je pense que nous pourrions utiliser une réponse mise à jour.

return (
  <th value={column} onClick={()=>this.handleSort(column)} >{column}</th>
);

Fondamentalement, (pour ceux qui ne le savent pas) puisque onClick s'attend à ce qu'une fonction lui soit transmise, bind fonctionne car il crée une copie d'une fonction. Au lieu de cela, nous pouvons passer une expression de fonction de flèche qui appelle simplement la fonction souhaitée et préserve this. Vous ne devriez jamais avoir besoin de lier la méthode render dans React, mais si, pour une raison quelconque, vous perdez this dans l'une de vos méthodes de composant:

constructor(props) {
  super(props);
  this.myMethod = this.myMethod.bind(this);
}
112
aikeru

[[h/t to @ E.Sundin for en reliant ceci dans un commentaire]

La première réponse (fonctions anonymes ou liaison) fonctionnera, mais ce n’est pas la plus performante, car elle crée une copie du gestionnaire d’événements pour chaque instance générée par la fonction map().

Ceci est une explication de la manière optimale de le faire à partir du ESLint-plugin-react :

Listes d'articles

Un cas d'utilisation courant de bind dans le rendu est lors du rendu d'une liste, pour avoir un rappel séparé par élément de liste:

const List = props => (
      <ul>
        {props.items.map(item =>
          <li key={item.id} onClick={() => console.log(item.id)}>
            ...
          </li>
        )}
      </ul>
    );

Plutôt que de le faire de cette façon, tirez la section répétée dans son propre composant:

const List = props => (
      <ul>
        {props.items.map(item =>
          <ListItem 
            key={item.id} 
            item={item} 
            onItemClick={props.onItemClick} // assume this is passed down to List
           />
        )}
      </ul>
    );


const ListItem = props => {
  const _onClick = () => {
    console.log(props.item.id);
  }
    return (
      <li onClick={_onClick}>
        ...
      </li>
    );

});

Cela accélérera le rendu, car il évite d'avoir à créer de nouvelles fonctions (par le biais d'appels de liaison) à chaque rendu.

69
Brandon

Ceci est mon approche, je ne sais pas à quel point c'est mauvais, veuillez commenter

Dans l'élément cliquable

return (
    <th value={column} onClick={that.handleSort} data-column={column}>   {column}</th>
);

puis

handleSort(e){
    this.sortOn(e.currentTarget.getAttribute('data-column'));
}
21
Santiago Ramirez

cet exemple pourrait être un peu différent du vôtre. mais je peux vous assurer que c'est la meilleure solution que vous puissiez avoir pour résoudre ce problème. J'ai cherché pendant des jours une solution sans problème de performances. et finalement venu avec celui-ci.

class HtmlComponent extends React.Component {
  constructor() {
    super();
    this.state={
       name:'MrRehman',
    };
    this.handleClick= this.handleClick.bind(this);
  }

  handleClick(event) {
    const { param } = e.target.dataset;
    console.log(param);
    //do what you want to do with the parameter
  }

  render() {
    return (
      <div>
        <h3 data-param="value what you wanted to pass" onClick={this.handleClick}>
          {this.state.name}
        </h3>
      </div>
    );
  }
}

UPDATE

si vous voulez traiter avec des objets qui sont supposés être les paramètres. vous pouvez utiliser JSON.stringify(object) pour la convertir en chaîne et l'ajouter à l'ensemble de données.

return (
          <div>
            <h3 data-param={JSON.stringify({name:'me'})} onClick={this.handleClick}>
              {this.state.name}
            </h3>
          </div>
        );
16
hannad rehman
class extends React.Component {
    onClickDiv = (column) => {
        // do stuff
    }
    render() {
        return <div onClick={() => this.onClickDiv('123')} />
    }
}
4

Une autre option n'impliquant pas .bind ou ES6 consiste à utiliser un composant enfant avec un gestionnaire pour appeler le gestionnaire parent avec les accessoires nécessaires. Voici un exemple (et un lien vers un exemple pratique est ci-dessous):

var HeaderRows = React.createClass({
  handleSort:  function(value) {
     console.log(value);
  },
  render: function () {
      var that = this;
      return(
          <tr>
              {this.props.defaultColumns.map(function (column) {
                  return (
                      <TableHeader value={column} onClick={that.handleSort} >
                        {column}
                      </TableHeader>
                  );
              })}
              {this.props.externalColumns.map(function (column) {
                  // Multi dimension array - 0 is column name
                  var externalColumnName = column[0];
                  return ( <th>{externalColumnName}</th>
                  );
              })}
          </tr>);
      )
  }
});

// A child component to pass the props back to the parent handler
var TableHeader = React.createClass({
  propTypes: {
    value: React.PropTypes.string,
    onClick: React.PropTypes.func
  },
  render: function () {
    return (
      <th value={this.props.value} onClick={this._handleClick}
        {this.props.children}
      </th>
    )        
  },
  _handleClick: function () {
    if (this.props.onClick) {
      this.props.onClick(this.props.value);
    }
  }
});

L'idée de base est que le composant parent transmette la fonction onClick à un composant enfant. Le composant enfant appelle la fonction onClick et peut accéder à tous les props qui lui ont été transmis (et au event), ce qui vous permet d'utiliser n'importe quelle valeur event ou d'autres éléments de la propriété du parent. onClick fonction.

Voici une démonstration de CodePen montrant cette méthode en action.

4
Brett DeWoody

Je me rends compte que la soirée a pris beaucoup de retard, mais je pense qu'une solution beaucoup plus simple pourrait satisfaire de nombreux cas d'utilisation:

    handleEdit(event) {
        let value = event.target.value;
    }

    ...

    <button
        value={post.id}
        onClick={this.handleEdit} >Edit</button>

Je suppose que vous pouvez également utiliser un attribut data-.

Simple, sémantique.

3
jhchnc

Implémentation du nombre total d'exposition à partir d'un objet en passant le nombre en tant que paramètre des composants principaux aux sous-composants, comme décrit ci-dessous.

Voici MainComponent.js

import React, { Component } from "react";

import SubComp from "./subcomponent";

class App extends Component {

  getTotalCount = (count) => {
    this.setState({
      total: this.state.total + count
    })
  };

  state = {
    total: 0
  };

  render() {
    const someData = [
      { name: "one", count: 200 },
      { name: "two", count: 100 },
      { name: "three", count: 50 }
    ];
    return (
      <div className="App">
        {someData.map((nameAndCount, i) => {
          return (
            <SubComp
              getTotal={this.getTotalCount}
              name={nameAndCount.name}
              count={nameAndCount.count}
              key={i}
            />
          );
        })}
        <h1>Total Count: {this.state.total}</h1>
      </div>
    );
  }
}

export default App;

Et voici SubComp.js

import React, { Component } from 'react';
export default class SubComp extends Component {

  calculateTotal = () =>{
    this.props.getTotal(this.props.count);
  }

  render() {
    return (
      <div>
        <p onClick={this.calculateTotal}> Name: {this.props.name} || Count: {this.props.count}</p>
      </div>
    )
  }
};

Essayez de mettre en œuvre ce qui précède et vous obtiendrez un scénario précis expliquant comment les paramètres de transmission fonctionnent dans les réactions de toutes les méthodes DOM.

2
nandu

J'ai ajouté du code pour la valeur d'événement onclick à la méthode de deux manières. 1 . using bind 2. en utilisant la méthode de la flèche (=>). voir les méthodes handlesort1 et handlesort

var HeaderRows  = React.createClass({
    getInitialState : function() {
      return ({
        defaultColumns : ["col1","col2","col2","col3","col4","col5" ],
        externalColumns : ["ecol1","ecol2","ecol2","ecol3","ecol4","ecol5" ],

      })
    },
    handleSort:  function(column,that) {
       console.log(column);
       alert(""+JSON.stringify(column));
    },
    handleSort1:  function(column) {
       console.log(column);
       alert(""+JSON.stringify(column));
    },
    render: function () {
        var that = this;
        return(
        <div>
            <div>Using bind method</div>
            {this.state.defaultColumns.map(function (column) {
                return (
                    <div value={column} style={{height : '40' }}onClick={that.handleSort.bind(that,column)} >{column}</div>
                );
            })}
            <div>Using Arrow method</div>

            {this.state.defaultColumns.map(function (column) {
                return (
                    <div value={column} style={{height : 40}} onClick={() => that.handleSort1(column)} >{column}</div>

                );
            })}
            {this.state.externalColumns.map(function (column) {
                // Multi dimension array - 0 is column name
                var externalColumnName = column;
                return (<div><span>{externalColumnName}</span></div>
                );
            })}

        </div>);
    }
});
2
Merugu Prashanth

J'ai écrit un composant d'emballage pouvant être réutilisé à cette fin, qui repose sur les réponses acceptées ici. Si tout ce que vous avez à faire est de passer une chaîne cependant, ajoutez simplement un attribut data et lisez-le à partir de e.target.dataset (comme certains l’ont suggéré). Par défaut, mon wrapper se liera à tout accessoire qui est une fonction et commence par "on" et renverra automatiquement les accessoires de données à l'appelant après tous les autres arguments d'événement. Bien que je ne l'aie pas testé pour ses performances, cela vous donnera la possibilité d'éviter de créer vous-même le cours, et il peut être utilisé comme ceci:

const DataButton = withData('button')

const DataInput = withData('input');

ou pour Composants et fonctions

const DataInput = withData(SomeComponent);

ou si vous préférez

const DataButton = withData(<button/>)

déclarez qu'en dehors de votre conteneur (près de vos importations)

Voici l'utilisation dans un conteneur:

import withData from './withData';
const DataInput = withData('input');

export default class Container extends Component {
    state = {
         data: [
             // ...
         ]
    }

    handleItemChange = (e, data) => {
        // here the data is available 
        // ....
    }

    render () {
        return (
            <div>
                {
                    this.state.data.map((item, index) => (
                        <div key={index}>
                            <DataInput data={item} onChange={this.handleItemChange} value={item.value}/>
                        </div>
                    ))
                }
            </div>
        );
    }
}

Voici le code de wrapper 'withData.js:

import React, { Component } from 'react';

const defaultOptions = {
    events: undefined,
}

export default (Target, options) => {
    Target = React.isValidElement(Target) ? Target.type : Target;
    options = { ...defaultOptions, ...options }

    class WithData extends Component {
        constructor(props, context){
            super(props, context);
            this.handlers = getHandlers(options.events, this);        
        }

        render() {
            const { data, children, ...props } = this.props;
            return <Target {...props} {...this.handlers} >{children}</Target>;
        }

        static displayName = `withData(${Target.displayName || Target.name || 'Component'})`
    }

    return WithData;
}

function getHandlers(events, thisContext) {
    if(!events)
        events = Object.keys(thisContext.props).filter(prop => prop.startsWith('on') && typeof thisContext.props[prop] === 'function')
    else if (typeof events === 'string')
        events = [events];

    return events.reduce((result, eventType) => {
        result[eventType] = (...args) => thisContext.props[eventType](...args, thisContext.props.data);
        return result;
    }, {});
}
2
SlimSim

Theres est un moyen très facile.

 onClick={this.toggleStart('xyz')} . 
  toggleStart= (data) => (e) =>{
     console.log('value is'+data);  
 }
1
Roma Mukherjee

Autre tentative de réponse à la question de OP, y compris les appels e.preventDefault ():

Lien rendu (ES6)

<a href="#link" onClick={(e) => this.handleSort(e, 'myParam')}>

Fonction du composant

handleSort = (e, param) => {
  e.preventDefault();
  console.log('Sorting by: ' + param)
}
1
Po Rith

J'ai ci-dessous 3 suggestions à cela sur JSX onClick Events -

  1. En fait, nous n'avons pas besoin d'utiliser .bind () ou la fonction Arrow dans notre code. Vous pouvez utiliser simplement dans votre code.

  2. Vous pouvez également déplacer l'événement onClick de th (ou ul) vers tr (ou li) pour améliorer les performances. En gros, vous aurez un nombre n "d'auditeurs d'événement" pour votre élément n li.

    So finally code will look like this:
    <ul onClick={this.onItemClick}>
        {this.props.items.map(item =>
               <li key={item.id} data-itemid={item.id}>
                   ...
               </li>
          )}
    </ul>
    

    // Et vous pouvez accéder à la méthode item.id dans la méthode onItemClick comme indiqué ci-dessous:

    onItemClick = (event) => {
       console.log(e.target.getAttribute("item.id"));
    }
    
  3. Je suis d'accord avec l'approche mentionnée ci-dessus pour la création de React Component séparé pour ListItem et List. Ce code de rendu a l’air intéressant, mais si vous avez 1 000 li, 1000 écouteurs d’événement seront créés. S'il vous plaît assurez-vous que vous ne devriez pas avoir beaucoup d'auditeur d'événement.

    import React from "react";
    import ListItem from "./ListItem";
    export default class List extends React.Component {
    
        /**
        * This List react component is generic component which take props as list of items and also provide onlick
        * callback name handleItemClick
        * @param {String} item - item object passed to caller
        */
        handleItemClick = (item) => {
            if (this.props.onItemClick) {
                this.props.onItemClick(item);
            }
        }
    
        /**
        * render method will take list of items as a props and include ListItem component
        * @returns {string} - return the list of items
        */
        render() {
            return (
                <div>
                  {this.props.items.map(item =>
                      <ListItem key={item.id} item={item} onItemClick={this.handleItemClick}/>
                  )}
                </div>
            );
        }
    
    }
    
    
    import React from "react";
    
    export default class ListItem extends React.Component {
        /**
        * This List react component is generic component which take props as item and also provide onlick
        * callback name handleItemClick
        * @param {String} item - item object passed to caller
        */
        handleItemClick = () => {
            if (this.props.item && this.props.onItemClick) {
                this.props.onItemClick(this.props.item);
            }
        }
        /**
        * render method will take item as a props and print in li
        * @returns {string} - return the list of items
        */
        render() {
            return (
                <li key={this.props.item.id} onClick={this.handleItemClick}>{this.props.item.text}</li>
            );
        }
    }
    
1
Reetesh Agrawal

Ci-dessous, l'exemple qui transmet la valeur à l'événement onClick.

J'ai utilisé la syntaxe es6. Remember in composant de classe composant fonction de flèche ne lie pas automatiquement, donc explicitement liaison dans le constructeur.

class HeaderRows extends React.Component {

    constructor(props) {
        super(props);
        this.handleSort = this.handleSort.bind(this);
    }

    handleSort(value) {
        console.log(value);
    }

    render() {
        return(
            <tr>
                {this.props.defaultColumns.map( (column, index) =>
                    <th value={ column } 
                        key={ index } 
                        onClick={ () => this.handleSort(event.target.value) }>
                        { column }
                    </th>
                )}

                {this.props.externalColumns.map((column, index)  =>
                    <th value ={ column[0] }
                        key={ index }>
                        {column[0]}
                    </th>
                )}
            </tr>
         );
    }
}
1
karan singh

Utilisation de la fonction flèche:

Vous devez installer l'étape 2:

npm installe babel-preset-stage-2:

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value=0
        }
    }

    changeValue = (data) => (e) => {
        alert(data);  //10
        this.setState({ [value]: data })
    }

    render() {
        const data = 10;
        return (
            <div>
                <input type="button" onClick={this.changeValue(data)} />
            </div>
        );
    }
}
export default App; 
1
sm chinna
class TableHeader extends Component {
  handleClick = (parameter,event) => {
console.log(parameter)
console.log(event)

  }

  render() {
    return (
      <button type="button" 
onClick={this.handleClick.bind(this,"dataOne")}>Send</button>
    );
  }
}
1
Anik Mazumder

Vous pouvez simplement le faire si vous utilisez ES6.

export default class Container extends Component {
  state = {
    data: [
        // ...
    ]
  }

  handleItemChange = (e, data) => {
      // here the data is available 
      // ....
  }
  render () {
     return (
        <div>
        {
           this.state.data.map((item, index) => (
              <div key={index}>
                  <Input onChange={(event) => this.handItemChange(event, 
                         item)} value={item.value}/>
              </div>
           ))
        }
        </div>
     );
   }
 }
1
Louis Alonzo
1. You just have to use an arrow function in the Onclick event like this: 

<th value={column} onClick={() => that.handleSort(theValue)} >{column}</th>

2.Then bind this in the constructor method:
    this.handleSort = this.handleSort.bind(this);

3.And finally get the value in the function:
  handleSort(theValue){
     console.log(theValue);
}
1
Juan David Arce

Je suppose que vous devrez lier la méthode à l’instance de classe de React. Il est plus sûr d’utiliser un constructeur pour lier toutes les méthodes dans React. Dans votre cas, lorsque vous transmettez le paramètre à la méthode, le premier paramètre est utilisé pour lier le contexte ‘this’ de la méthode. Vous ne pouvez donc pas accéder à la valeur à l’intérieur de la méthode.

1
Andysenclave

Vous pouvez utiliser votre code comme ceci:

<th value={column} onClick={(e) => that.handleSort(e, column)} >{column}</th>

Ici, e est un objet d’événement, si vous voulez utiliser des méthodes d’événement telles que preventDefault() dans votre fonction de descripteur ou si vous voulez obtenir une valeur cible ou un nom tel que e.target.name.

0
Bankim Sutaria

Il y a 3 façons de gérer cela: -

  1. Lier la méthode en constructeur en tant que: -

    export class HeaderRows extends Component {
       constructor() {
           super();
           this.handleSort = this.handleSort.bind(this);
       }
    }
    
  2. Utilisez la fonction de flèche en la créant comme: -

    handleSort = () => {
        // some text here
    }
    
  3. La troisième voie est la suivante: -

    <th value={column} onClick={() => that.handleSort} >{column}</th>
    
0
Mansi Teharia

J'ai trouvé la solution de passer param comme attribut d'une balise tout à fait raisonnable.

Cependant, il a des limites:

  • Ne fonctionne pas correctement lorsque l'élément de liste a d'autres balises (ainsi, event.target peut être différent de celui prévu)
  • Param pourrait être une chaîne uniquement. Requiert la sérialisation et la désérialisation.

C'est pourquoi j'ai proposé cette bibliothèque: react-event-param

Il:

  • Résoudre le problème des enfants en recherchant l'attribut nécessaire chez les parents à tout moment
  • Sérialise et désérialise automatiquement les paramètres
  • Encapsule la logique de réglage et d’obtention. Pas besoin de jouer avec les noms de param

Exemple d'utilisation:

import { setEventParam, getEventParam } from "react-event-param";

class List extends Component {
  onItemClick = e => {
    const index = getEventParam(e.target);
    // Do something with index
  };

  render() {
    return (
      <ul>
        {this.props.items.map((itemText, index) => (
          <li
            key={index}
            {...setEventParam(index)}
            onClick={this.onItemClick}
          >
            {{ itemText }}
          </li>
        ))}
      </ul>
    );
  }
}

export default List;
0
sneas