3

I am trying to create a list which has following features.

  1. On hover change background color of the listItem.
  2. On Click change background color of a listItem.
  3. Toggle background color between clicked elements.[i.e. Only one element in the list can have clicked property]

I have executed onhover 1 and 2 features, but I am not able to implement the 3rd feature. Please help me to solve this problem.

Thanks in advance.

/** @jsx React.DOM */

   'use strict'

    var React = require('react')


    var ListItem = React.createClass({

        getInitialState: function() {
            return  {hover_flag: false, click_flag: false}
        },

        hoverEvent: function() {
            this.setState({hover_flag: !this.state.hover_flag})
        },

        clickEvent: function(){
            this.setState({click_flag: true})
        },

        render: function() {
            var liStyle = {
                /* some more class properties */
                background: '#cc181e',
            }

            if(this.state.hover_flag || this.state.click_flag) {
                liStyle['background'] = '#880000'
            }

            if(this.state.click_flag) {
                liStyle['background'] = '#880000'           
            }

            return (
                <li onClick={this.clickEvent} onMouseEnter={this.hoverEvent} onMouseLeave={this.hoverEvent} style={liStyle} key={this.props.name}>{this.props.name}</li>
            )
        }

    })


    module.exports = React.createClass({

        render: function() {

            var ulStyle = {
                padding: '0px',
                margin: '20px',
            }

            var link = {
                textDecoration: 'none',
                color: 'white',
                cursor: 'pointer'       
            }


            var list = this.props.data.map(function(data) {
                /*List of li elements */
                return <ListItem name={data.name} />
            })

            return (

                <ul style={ulStyle}>
                    {list}
                </ul>

            )
        }

    });
maskacovnik
  • 3,080
  • 5
  • 20
  • 26
Vamshi Kolanu
  • 409
  • 2
  • 5
  • 16

2 Answers2

6

Before looking at any code, think about the actual cause of the problem. In your current implementation, each ListItem maintains its own click_flag state. When one ListItem is clicked, it sets its own click_flag to true, but this does not trigger the other ListItems to reset their own click_flag to false. This is the cause of the problem. The solution is to pass the click_flag as a props from the parent to each ListItem. It is the parent's responsibility to ensure that only ListItem gets prop as true, while the others are false. Likewise, it is the ListItem's responsibility to notify the parent when it has been clicked via a callback props passed down from the parent.

So, the ListItem looks like:

var ListItem = React.createClass({
    propTypes: {
        onClick: React.PropTypes.func.isRequired,
        isSelected: React.PropTypes.bool
    },
    getDefaultProps: function() {
        return {
            isSelected: false
        };
    },
    getInitialState: function() {
        return {
            hover_flag: false
        };
    },
    hoverEvent: function() {
        this.setState({hover_flag: !this.state.hover_flag});
    },
    render: function() {
        var liStyle = {
            background: '#cc181e'
        };
        if (this.props.isSelected || this.state.hover_flag) {
            liStyle['background'] = '#880000';
        }
        return (
            <li
                onClick={this.props.onClick}
                onMouseEnter={this.hoverEvent}
                onMouseLeave={this.hoverEvent}
                style={liStyle}>{this.props.name}
            </li>
        );
    }
});

And, the parent could look like this:

module.exports = React.createClass({
    getInitialState: function() {
        return {
            selectedItem: null
        };
    },
    clickHandler: function(idx) {
        this.setState({selectedItem: idx});
    },
    render: function() {
        var ulStyle = {
            padding: '0px',
            margin: '20px'
        };
        var items = this.props.data.map(function (item, idx) {
            var is_selected = this.state.selectedItem == idx;
            return <ListItem
                key={item.name}
                name={item.name}
                onClick={this.clickHandler.bind(this, idx)}
                isSelected={is_selected}
                />;
        }.bind(this));
        return (
            <ul style={ulStyle}>
                {items}
            </ul>
        );
    }
});

The parent maintains the state variable which stores which ListItem is the current selected one. It uses this state in render() to pass is_selected = true to only one ListItem, and all the others are passed false. The parent's state is updated by the clickHandler which is passed down as a props to each ListItem. See example fiddle here

FriC
  • 786
  • 10
  • 14
1

Since there will be only one selected item the parent component can maintain a state with the selected item index.

Parent Component

getInitialState: function(){
  return {selectedItem : null, items: this.props.data};
}

Change the render method to something like this (notice the isSelected property that is being sent to the ListItem component)

var selectedItem = this.state.selectedItem;
var list = this.state.items.map(function(data) {
  /*List of li elements */
  return <ListItem name={data.name} onSelect={this.onSelectHandler} isSelected={selectedItem === data.id} key={data.id}/> // <--- I would suggest having a unique ID
});

Lets implement onSelectHandler later.

ListItem

In the definition transfer the prop isSelected to ListItem's state

getInitialState: function(){
  return  {hover_flag: false, click_flag: this.props.isSelected} // <---- notice the change here
}

Now modify the clickEvent and trigger ListItem's onSelect property and send the clicked ListItem's name (i would suggest a unique ID here too)

clickEvent: function(){
  this.setState({click_flag: true});
  this.props.onSelect(this.props.name); // <--- I would suggest sending ID
}

Lets implement onSelectHandler for the parent component now

onSelectHandler: function(childItemID){
  this.setState({selectedItem: childItemID});
}

Hope this makes sense

Dhiraj
  • 33,140
  • 10
  • 61
  • 78