130

I have

var TestApp = React.createClass({
      getComponent: function(){
          console.log(this.props);
      },
      render: function(){
        return(
             <div>
             <ul>
                <li onClick={this.getComponent}>Component 1</li>
             </ul>
             </div>
        );
      }
});
React.renderComponent(<TestApp />, document.body);

I want to color the background of the clicked list element. How can I do this in React ?

Something like

$('li').on('click', function(){
    $(this).css({'background-color': '#ccc'});
});
Lumi Lu
  • 3,289
  • 1
  • 11
  • 21
user544079
  • 16,109
  • 42
  • 115
  • 171

11 Answers11

104

Why not:

onItemClick: function (event) {

    event.currentTarget.style.backgroundColor = '#ccc';

},

render: function() {
    return (
        <div>
            <ul>
                <li onClick={this.onItemClick}>Component 1</li>
            </ul>
        </div>
    );
}

And if you want to be more React-ive about it, you might want to set the selected item as state of its containing React component, then reference that state to determine the item's color within render:

onItemClick: function (event) {

    this.setState({ selectedItem: event.currentTarget.dataset.id });
    //where 'id' =  whatever suffix you give the data-* li attribute
},

render: function() {
    return (
        <div>
            <ul>
                <li onClick={this.onItemClick} data-id="1" className={this.state.selectedItem == 1 ? "on" : "off"}>Component 1</li>
                <li onClick={this.onItemClick} data-id="2" className={this.state.selectedItem == 2 ? "on" : "off"}>Component 2</li>
                <li onClick={this.onItemClick} data-id="3" className={this.state.selectedItem == 3 ? "on" : "off"}>Component 3</li>
            </ul>
        </div>
    );
},

You'd want to put those <li>s into a loop, and you need to make the li.on and li.off styles set your background-color.

Scott Martin
  • 1,260
  • 2
  • 17
  • 27
ericsoco
  • 24,913
  • 29
  • 97
  • 127
  • 2
    **Manual DOM manipulation in React is an anti-pattern** which only leads to more problems. Avoid stuff like `event.currentTarget.style.backgroundColor = '#ccc';` unless you really understand what you're doing (most of the times, while integrating third-party widgets). – Emile Bergeron Jan 24 '20 at 23:58
62

Two ways I can think of are

var TestApp = React.createClass({
    getComponent: function(index) {
        $(this.getDOMNode()).find('li:nth-child(' + index + ')').css({
            'background-color': '#ccc'
        });
    },
    render: function() {
        return (
            <div>
              <ul>
                <li onClick={this.getComponent.bind(this, 1)}>Component 1</li>
                <li onClick={this.getComponent.bind(this, 2)}>Component 2</li>
                <li onClick={this.getComponent.bind(this, 3)}>Component 3</li>
              </ul>
            </div>
        );
    }
});
React.renderComponent(<TestApp /> , document.getElementById('soln1'));

This is my personal favorite.

var ListItem = React.createClass({
    getInitialState: function() {
        return {
            isSelected: false
        };
    },
    handleClick: function() {
        this.setState({
            isSelected: true
        })
    },
    render: function() {
        var isSelected = this.state.isSelected;
        var style = {
            'background-color': ''
        };
        if (isSelected) {
            style = {
                'background-color': '#ccc'
            };
        }
        return (
            <li onClick={this.handleClick} style={style}>{this.props.content}</li>
        );
    }
});

var TestApp2 = React.createClass({
    getComponent: function(index) {
        $(this.getDOMNode()).find('li:nth-child(' + index + ')').css({
            'background-color': '#ccc'
        });
    },
    render: function() {
        return (
            <div>
             <ul>
              <ListItem content="Component 1" />
              <ListItem content="Component 2" />
              <ListItem content="Component 3" />
             </ul>
            </div>
        );
    }
});
React.renderComponent(<TestApp2 /> , document.getElementById('soln2'));

Here is a DEMO

I hope this helps.

Dhiraj
  • 33,140
  • 10
  • 61
  • 78
  • 8
    It's not recommended to apply the bind within the render function since it will do it every time the component is rendered. you can move it to some function which runs at the start of the lifecycle – jony89 Apr 30 '17 at 14:44
  • 1
    @jony89 agreed if `.bind` doesn't take an extra parameter. But in the first case it does. I don't think there is another way – Dhiraj Apr 30 '17 at 17:44
  • 1
    There is, create three different functions (which created by the result of getComponent.bind(this, 1)), though that definite can be a decision (would do it for 2-3 components, not for 20 - unless it's really performance issue and easy to create it dynamically). – jony89 May 01 '17 at 18:25
39

Here is how you define a react onClick event handler, which was answering the question title... using es6 syntax

import React, { Component } from 'react';

export default class Test extends Component {
  handleClick(e) {
    e.preventDefault()
    console.log(e.target)
  }

  render() {
    return (
      <a href='#' onClick={e => this.handleClick(e)}>click me</a>
    )
  }
}
svnm
  • 22,878
  • 21
  • 90
  • 105
  • 9
    Neither `bind` nor arrow functions should be used within `render` methods because they result in a new function being created each time. This has the effect changing the component's state, and components with changed state are always re-rendered. For a single `a` this is no big deal. For generated lists with clickable items this becomes a big deal very quickly. This is why it's specifically warned against. – hippietrail Sep 23 '17 at 02:16
18

Use ECMA2015. Arrow functions make "this" a lot more intuitive.

import React from 'react';


class TestApp extends React.Component {
   getComponent(e, index) {
       $(e.target).css({
           'background-color': '#ccc'
       });
   }
   render() {
       return (
           <div>
             <ul>
               <li onClick={(e) => this.getComponent(e, 1)}>Component 1</li>
               <li onClick={(e) => this.getComponent(e, 2)}>Component 2</li>
               <li onClick={(e) => this.getComponent(e, 3)}>Component 3</li>
             </ul>
           </div>
       );
   }
});
React.renderComponent(<TestApp /> , document.getElementById('soln1'));`
itcropper
  • 752
  • 1
  • 7
  • 21
13

If you're using ES6, here's some simple example code:

import React from 'wherever_react_is';

class TestApp extends React.Component {

  getComponent(event) {
      console.log('li item clicked!');
      event.currentTarget.style.backgroundColor = '#ccc';
  }

  render() {
    return(
       <div>
         <ul>
            <li onClick={this.getComponent.bind(this)}>Component 1</li>
         </ul>
       </div>
    );
  }
}

export default TestApp;

In ES6 class bodies, functions no longer require the 'function' keyword and they don't need to be separated by commas. You can also use the => syntax as well if you wish.

Here's an example with dynamically created elements:

import React from 'wherever_react_is';

class TestApp extends React.Component {

constructor(props) {
  super(props);

  this.state = {
    data: [
      {name: 'Name 1', id: 123},
      {name: 'Name 2', id: 456}
    ]
  }
}

  getComponent(event) {
      console.log('li item clicked!');
      event.currentTarget.style.backgroundColor = '#ccc';
  }

  render() {        
       <div>
         <ul>
         {this.state.data.map(d => {
           return(
              <li key={d.id} onClick={this.getComponent.bind(this)}>{d.name}</li>
           )}
         )}
         </ul>
       </div>
    );
  }
}

export default TestApp;

Note that each dynamically created element should have a unique reference 'key'.

Furthermore, if you would like to pass the actual data object (rather than the event) into your onClick function, you will need to pass that into your bind. For example:

New onClick function:

getComponent(object) {
    console.log(object.name);
}

Passing in the data object:

{this.state.data.map(d => {
    return(
      <li key={d.id} onClick={this.getComponent.bind(this, d)}>{d.name}</li>
    )}
)}
falsarella
  • 12,217
  • 9
  • 69
  • 115
dirtigr00vz
  • 259
  • 3
  • 8
  • I am trying to build my li items dynamic and then this sees to become undefined and the onClick function therefore throws an error. – landed Feb 23 '16 at 21:13
  • 1
    I just found a similar answer where you need to use .bind(this)); at the end of the anonymous function as this here refers to window until you make the bind... – landed Feb 23 '16 at 22:01
  • 1
    [No, you should never use `bind` or arrow functions in JSX.](https://stackoverflow.com/questions/36677733/why-jsx-props-should-not-use-arrow-functions) – hippietrail Sep 23 '17 at 02:18
6

Handling events with React elements is very similar to handling events on DOM elements. There are some syntactic differences:

  • React events are named using camelCase, rather than lowercase.
  • With JSX you pass a function as the event handler, rather than a string.

So as mentioned in React documentation, they quite similar to normal HTML when it comes to Event Handling, but event names in React using camelcase, because they are not really HTML, they are JavaScript, also, you pass the function while we passing function call in a string format for HTML, they are different, but the concepts are pretty similar...

Look at the example below, pay attention to the way event get passed to the function:

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}
Alireza
  • 100,211
  • 27
  • 269
  • 172
3

import React from 'react';

class MyComponent extends React.Component {

  getComponent(event) {
      event.target.style.backgroundColor = '#ccc';
      
      // or you can write
      //arguments[0].target.style.backgroundColor = '#ccc';
  }

  render() {
    return(
       <div>
         <ul>
            <li onClick={this.getComponent.bind(this)}>Component 1</li>
         </ul>
       </div>
    );
  }
}

export { MyComponent };  // use this to be possible in future imports with {} like: import {MyComponent} from './MyComponent'
export default MyComponent;
Jalaleddin Hosseini
  • 2,142
  • 2
  • 25
  • 28
2

class FrontendSkillList extends React.Component {
  constructor() {
    super();
    this.state = { selectedSkill: {} };
  }
  render() {
    return (
      <ul>
        {this.props.skills.map((skill, i) => (
            <li
              className={
                this.state.selectedSkill.id === skill.id ? "selected" : ""
              }
              onClick={this.selectSkill.bind(this, skill)}
              style={{ cursor: "pointer" }}
              key={skill.id}
            >
            {skill.name}
            </li>
        ))}
      </ul>
    );
  }

  selectSkill(selected) {
    if (selected.id !== this.state.selectedSkill.id) {
      this.setState({ selectedSkill: selected });
    } else {
      this.setState({ selectedSkill: {} });
    }
  }
}

const data = [
  { id: "1", name: "HTML5" },
  { id: "2", name: "CSS3" },
  { id: "3", name: "ES6 & ES7" }
];
const element = (
  <div>
    <h1>Frontend Skill List</h1>
    <FrontendSkillList skills={data} />
  </div>
);
ReactDOM.render(element, document.getElementById("root"));
.selected {
  background-color: rgba(217, 83, 79, 0.8);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>

@user544079 Hope this demo can help :) I recommend changing background color by toggling classname.

dabeng
  • 1,340
  • 17
  • 7
2

import React from 'react';

class MyComponent extends React.Component {

  getComponent(event) {
      event.target.style.backgroundColor = '#ccc';
      
      // or you can write
      //arguments[0].target.style.backgroundColor = '#ccc';
  }

  render() {
    return(
       <div>
         <ul>
            <li onClick={this.getComponent.bind(this)}>Component 1</li>
         </ul>
       </div>
    );
  }
}

export { MyComponent };  // use this to be possible in future imports with {} like: import {MyComponent} from './MyComponent'
export default MyComponent;
  • can you provide more context to this code by explaining how this could fix the issue? – MEDZ Feb 01 '20 at 08:36
1

You can make use of the React.createClone method. Create your element, than create a clone of it. During the clone's creation, you can inject props. Inject an onClick : method prop like this

{ onClick : () => this.changeColor(originalElement, index) }

the changeColor method will set the state with the duplicate, allowing you sto set the color in the process.

render()
  {
    return(
      <ul>

        {this.state.items.map((val, ind) => {
          let item = <li key={ind}>{val}</li>;
          let props = { 
            onClick: () => this.Click(item, ind),
            key : ind,
            ind
          }
          let clone = React.cloneElement(item, props, [val]);
          return clone;
        })}

      </ul>
    )
  }
Eddie D
  • 1,120
  • 7
  • 16
-18

This is a non-standard (but not so uncommon) React pattern that doesn't use JSX, instead putting everything inline. Also, it's Coffeescript.

The 'React-way' to do this would be with the component's own state:

(c = console.log.bind console)

mock_items: [
    {
        name: 'item_a'
        uid: shortid()
    }
    {
        name: 'item_b'
        uid: shortid()
    }
    {
        name: 'item_c'
        uid: shortid()
    }
]
getInitialState: ->
    lighted_item: null
render: ->
    div null,
        ul null,
            for item, idx in @mock_items
                uid = item.uid
                li
                    key: uid
                    onClick: do (idx, uid) =>
                        (e) =>
                            # justf to illustrate these are bound in closure by the do lambda,
                            c idx
                            c uid
                            @setState
                                lighted_item: uid
                    style:
                        cursor: 'pointer'
                        background: do (uid) =>
                            c @state.lighted_item
                            c 'and uid', uid
                            if @state.lighted_item is uid then 'magenta' else 'chartreuse'
                        # background: 'chartreuse'
                    item.name

This example works -- I tested it locally. You can check out this example code exactly at my github. Originally the env was only local for my own whiteboard r&d purposes but I posted it to Github for this. It may get written over at some point but you can check out the commit from Sept 8, 2016 to see this.

More generally, if you want to see how this CS/no-JSX pattern for React works, check out some recent work here. It's possible I will have time to fully implement a POC for this app idea, the stack for which includes NodeJS, Primus, Redis, & React.

Andreas
  • 2,455
  • 10
  • 21
  • 24
Wylie Kulik
  • 696
  • 5
  • 17
  • the background need not be a `do` lambda: This expression works also: `background: if @state.lighted_item is uid then 'magenta' else 'chartreuse'` – Wylie Kulik Sep 08 '16 at 13:38
  • hello how can i view onclick on browser console? – Muneem Habib Nov 25 '16 at 19:33
  • Here my `c = console.log.bind console` – Wylie Kulik Nov 27 '16 at 13:01
  • 13
    Why would you use CoffeeScript in an answer to a question that doesn't mention it in any way? It doesn't make any sense and it can probably make the answer harder to read for the asker, as he might not know/like CoffeeScript. Downvoting, obviously. – macbem Mar 07 '17 at 21:55
  • Coffee is just JS, in the same way babel compiled ES7 is just JS and not a separate language. – Wylie Kulik Mar 08 '17 at 05:36
  • 7
    No but it is something built on top of the language, is absolutelty not standard and requires installing, and compiling. it was a truly a poor choice to write your answer in coffeescript when there is ZERO hinting at all that they are using coffeescript in their project – TheRealMrCrowley Mar 15 '17 at 06:54
  • 4
    Coffeescript is just *a layer on top of* js. FTFY. – machineghost May 09 '17 at 23:42