13

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')
);

1 Answer 1

9
  • 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
      });
    },
Sign up to request clarification or add additional context in comments.

9 Comments

I forgot to remove the jQuery function, but my code no longer has it... From the React docs here it seems to encourage using props for any data that can be computed via other state or props. Is there a benefit to using state vs props? The way I currently have it setup as props and the functionality is working.
I updated my code, ssorallen... Does this seem "React-ish" to you? Also, currently the search filter updates the checkbox inputs that are on the DOM. If a checkbox is checked and then the search filter updates the DOM, how can I store the value of the checked checkbox in React? Would I have to use jQuery to grab and store that data on the client before sending it to the server?
P.S. I know the CategoriesPanel code needs some refactoring, I'm going to refactor it just wanted to get everything working first. Thanks again for the help.
setProps can only be called on a root component, a component rendered via React.renderComponent. That is possible so the thing calling render, which is not a React component itself, can update the data in the component. A component should not call setProps on itself, even if it happens to be the root.
It is best practice to have data flow in a single direction and events be the source of data changes. Functionally state and props will work the same in your example, but in general props is an immutable structure passed from a parent component to a child component; state is a mutable structure maintained by a component. Your example is using props the way state is intended to be used.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.