Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle click events on child components in React.js, is there a React "way"?

Tags:

reactjs

I'm attempting to create/prototype a widget that has a search filter and 2-panels that have a key-value/parent-child relationship. The right panel contains categories, the left panel has interests that are associated with an individual category. The category rows are selectable to show the associated interest checkboxes. I haven't gotten to the point of trying to understand how to update the form submission with the checkboxes, first I need to understand more about the correct way to make the data flow.

This is my first foray into React and I know that React encourages one-way data flow. I feel that I am updating the interests panel incorrectly by using jQuery to handle the events. I'd like to know some alternative ways to update the interests panel.

From what I've read, in order to update parent (I believe its referred to as owner in the React docs) components you need to define callbacks on the child/ownee components. I'd appreciate an explanation of this and also if I'm incorrectly using interests in state vs props. I was attempting to only have the Search filter & the checkboxes be a part of the state for the widget, because I read that filterable data should belong in props.

UPDATE: I changed the interests to be initialized in getDefaultProps, so now I'm using them as props. I think this is more the React way, am I correct? I also changed the way that interests are updated by changing the handleCategoryRowClick function. I think I am doing things the more React way now, but would still love any advice that anyone with more experience using React could give, thank you.

Any help would be greatly appreciated!

/** @jsx React.DOM */

var InterestRow = React.createClass({
    render: function() {
        return (
            <div>
                <label>{this.props.interest.name}</label>
                <input type="checkbox" ref={this.props.key} value={this.props.key} />
            </div>
        )
   }
});

var InterestPanel = React.createClass({
    var interestPanel = this;
    var rows = [];

    render: function() {
        var rows = [];
        var i = 0;

        _(this.props.interests).each(function (interest) {
            rows.push(<InterestRow interest={interest} key={interest.value} />);
        });

        return (
            <form>{rows}</form>
        )
    }
});

var CategoryRow = React.createClass({
    render: function() {
        return (
            <li onClick={this.props.handleClick.bind(null, this.props.interests)}>{this.props.category}</li>
        )
    }
});

var CategoriesPanel = React.createClass({
render: function() {
    var categoriesPanel = this;
    var rows = [];
    var categories = [];
    var interests = [];
    var interestSet = [];
    var missedSet = [];
    var lastCategory = null;
    var category;
    var interest;
    var i = 0;

    _.each(categoriesPanel.props.data, function (datum) {
        name = datum.name;
        value = datum.targeting_value;
        category = name.split('/')[0];
        interest = {name: name.split('/')[1], value: value}

        if (_.contains(interest.name.toLowerCase(), categoriesPanel.props.searchText.toLowerCase())) {
            if (category !== lastCategory) {
                if (interestSet.length > 0) {
                    interests.push(interestSet.concat(missedSet));
                }

                lastCategory = category;
                categories.push(category);
                interestSet = [];
                interestSet.push(interest);
                missedSet = [];
            } else {
                if (!_.contains(categories, category)) {
                    categories.push(category);
                    interestSet.push(interest);
                } else {
                    interestSet.push(interest);
                }
            }
        } else {
            if (category !== lastCategory) {
                if (interestSet.length > 0) {
                    interests.push(interestSet.concat(missedSet));
                }

                lastCategory = category;
                interestSet = [];
                missedSet = [];
                missedSet.push(interest);
            } else {
                missedSet.push(interest);
            }
        }
    });

    if (interestSet.length > 0) {
        interests.push(interestSet.concat(missedSet));
    }

    var interestsObject = _.zipObject(categories, interests);

    _.each(interestsObject, function (interestSet, category) {
        i++;
        rows.push(<CategoryRow category={category} key={i} interests={interestSet} handleClick={categoriesPanel.props.handleClick} />)
    });

    return (
        <div>
            <ul>{rows}</ul>
        </div>
    )
}
});

var SearchBar = React.createClass({
    handleChange: function() {
        this.props.onUserInput(
            this.refs.searchTextInput.getDOMNode().value
        )
    },
    render: function() {
        return (
            <form onSubmit={this.handleSubmit}>
                <input
                    type="text"
                    placeholder="Search Interests..."
                    value={this.props.searchText}
                    ref="searchTextInput"
                    onChange={this.handleChange}
                />
            </form>
        );
     }
});

var InterestsTable = React.createClass({
loadDataFromTwitter: function() {
    $.ajax({
        url: this.props.url,
        dataType: 'json',
        success: function(data) {
            this.setProps({data: data});
        }.bind(this)
    });
},
getInitialState: function() {
   return {
       searchText: ''
   }
},
getDefaultProps: function() {
   return {
       data: [],
       interests: []
   }
},
componentWillMount: function() {
    this.loadDataFromTwitter();
},
handleUserInput: function(searchText) {
   this.setState({
       searchText: searchText
   });
},
handleCategoryRowClick: function(interests) {
    this.setProps({
        interests: interests
    });
},
render: function() {
    return (
        <div>
            <SearchBar
                searchText={this.state.searchText}
                onUserInput={this.handleUserInput}
            />
            <CategoriesPanel
                data={this.props.data}
                searchText={this.state.searchText}
                handleClick={this.handleCategoryRowClick}
            />
            <InterestsPanel
                interests={this.props.interests}
                searchText={this.state.searchText}
            />
        </div>
    );
}
});

React.renderComponent(
     <InterestsTable url="/api/interests" />,
    document.getElementById('content')
);
like image 302
evkline Avatar asked May 06 '14 02:05

evkline


People also ask

How can we pass an event handler to the child component in React?

To pass an onChange event handler to a child component in React: Define the event handler function in the parent component. Pass it as a prop to the child component, e.g. <Child handleChange={handleChange} /> . Set it to the onChange prop on the input field in the child.

How do you listen for click events that are outside of a component React?

We can use the createRef() method to create a reference for any element in the class-based component. Then we can check whether click event occurred in the component or outside the component. In the functional component, we can use the useRef() hook to create a reference for any element.

How do you add onClick events to React component?

Adding EventsReact events are written in camelCase syntax: onClick instead of onclick . React event handlers are written inside curly braces: onClick={shoot} instead of onClick="shoot()" .


1 Answers

  • There is some lingering jQuery that can be removed:
    componentDidMount: function() {
      $(document).on("handleCategoryRowClick", this.handleCategoryRowClick);
    },
  • Treat props as immutable as often as possible and React life cycle methods will work as expected. In this case, interests should be state instead of props because the InterestsTable component owns and modifies it; it's not passed in by the component's parent:
    getInitialState: function() {
      return {
        interests: [],
        searchText: ""
      };
    },
    handleCategoryRowClick: function(e, interests) {
      this.setState({
        interests: interests
      });
    },
like image 56
Ross Allen Avatar answered Oct 10 '22 06:10

Ross Allen