leosok
leosok

Reputation: 332

React.js - how to create Accordeon (talking to siblings)

I just started using react.js and i love the way components work. I created a Card component, which will add a css-class expanded on click. This works like a charm. Now I want all the other cards to loose there expanded property once another card is clicked. In jQuery i'd use siblings to do this.

What is the proper solution with react.js?

Here's my code:

var Card = React.createClass({

    getInitialState: function () {
        return {
            expanded: false,
            //clickCount: 0
        };
    },

    handleClick: function (event) {
        this.setProps({expanded: !this.props.expanded});
    },

    render: function () {
        var cx = React.addons.classSet;
        var classes = cx({
            'card': true,
            'expanded': this.state.expanded
        });

        return (
            <li className={classes} onClick={this.handleClick}>
                <h1>{this.props.name}</h1>
                <h2>{this.props.entf.toFixed(2)} km</h2>
            </li>
        )
    }

});


var App = React.createClass({

    handleClick: function (event) {
        console.log("clicked");
    },

    render: function () {

        var pools = this.props.data.map(function (pool) {
            return (
                <Card onClick={this.handleClick} name={pool.name} entf={pool.entf} />
            );
        });

        return (<ul>
                {pools}
        </ul>
        )
    }
});

Upvotes: 1

Views: 1209

Answers (2)

Miles
Miles

Reputation: 283

Isn't React super?

To get the behaviour you want without too much modification, I suggest passing a callback onClick or onExpand to each <Card/>. When a Card is activated, it runs the callback which can set a variable (perhaps activeCard) in App's state. In App's render method, modify the map function to give each Card an expanded boolean prop, which will be true depending on if the card's name/id matches activeCard. Use the new expanded prop instead of this.state.expanded in each Card's logic.

var Card = React.createClass({

render: function () {
    var cx = React.addons.classSet;
    var classes = cx({
        'card': true,
        'expanded': this.props.expanded
    });

    return (
        <li className={classes} onClick={this.props.onClick}>
            <h1>{this.props.name}</h1>
            <h2>{this.props.entf.toFixed(2)} km</h2>
        </li>
    )
}

});


var App = React.createClass({

getInitialState: function () {
    return { activeCard: '' }
},

render: function () {

    var pools = this.props.data.map(function (pool) {
        return (
            <Card onClick={this.handleClick.bind(this,pool.name)} name={pool.name} entf={pool.entf} expanded={pool.name == this.state.activeCard} />
        );
    }.bind(this));

    return (<ul>
            {pools}
    </ul>
    )
},

handleClick: function (value) {
    this.setState({ activeCard: value })
}
});

When working in React, think about how to keep components as stateless as possible. In paradigms like jQuery these pieces often manage themselves, but if you want to use React's potential then always keep an eye out for how to 'trickle' down state.

Upvotes: 0

nilgun
nilgun

Reputation: 10629

Instead of using siblings, you should keep an identifier to the expanded card in the App component's state and let React re-render if the selected card changes.

Here is an example how you would do it with react:

JSfiddle: http://jsfiddle.net/vdw27xpf/

var Card = React.createClass({

    render: function () {
        var cx = React.addons.classSet;
        var classes = cx({
            'card': true,
            'expanded': this.props.expanded
        });

        return (
            <li className={classes} onClick={this.props.onClick}>
                <h1>{this.props.name}</h1>
                <h2>{this.props.entf.toFixed(2)} km</h2>
            </li>
        )
    }

});


var App = React.createClass({

    getInitialState : function(){
        return { expandedCardName : null };
    },

    handleClick: function (value) {
       this.setState({ expandedCardName : value });
    },

    render: function () {

        var pools = this.props.data.map(function (pool) {
            return (
                <Card key={pool.name}
                      expanded = { pool.name == this.state.expandedCardName }
                      onClick={this.handleClick.bind(this,pool.name)} 
                      name={pool.name} entf={pool.entf} />
            );
        }.bind(this));

        return (<ul>
                {pools}
            </ul>
        )
    }
});
var data = [ { name:"card1", entf:60 },{ name:"card2", entf:50 } ]; 
React.render(<App data={data} />, document.body);

Upvotes: 2

Related Questions