Exercise 3: Solution
Need to add the click handlers, etc.
How do I get which tab was clicked?
////////////////////////////////////////////////////////////////////////////////
// Excercise:
// - make these tabs work when you click them
////////////////////////////////////////////////////////////////////////////////
var React = require('react');
var assign = require('react/lib/Object.assign');
var DATA = [..];
var App = React.createClass({
// 9. ++++ finally we can define the getInitialState method
// and the this.state.activeTabIndex inside it
getInitialState () {
return {
activeTabIndex: 0
}
},
// 3. ++++ Add the click handler
handleTabClick (activeTabIndex) {
// 11. ++-- change to activeTabIndex
this.setState({activeTabIndex: activeTabIndex });
// or do it the ES6 way:
// this.setState({ activeTabIndex });
},
renderTabs () {
// 4. ++++ Actually moves out tabIndex variable
var ativeTabIndex = 0;
return this.props.countries.map((country, index) => {
return (
// 1. ++++ add the click event
// 2. ++++ bind the handler to pass the index
// 5. ++++ changes style={index === 0 to index === activeTabIndex
// 10. ++-- changes all this to use the state
// ----<div onClick={this.handleTabClick.bind(this, index)} style={index === activeTabIndex ? styles.activeTab : styles.tab}>
{country.name}
<div
// rewrite the onClick handler
onClick={this.handleTabClick.bind(this, index)}
style={index === activeTabIndex ? styles.activeTab : styles.tab}>
// 12. ++++ add a key to remove the React warning (so it can track the nodes)
key={country.name}
>
{country.name}
</div>
);
});
},
renderPanel () {
// 6. ++++ use the activeTabIndex here as well
// 8. ++-- changes activeTabIndex = 0 to activeTabIndex = this.state.activeTabIndex
var activeTabIndex = this.state.activeTabIndex;
// 7. ++-- changes countries[0] to countries[activeTabIndex]
var country = this.props.countries[activeTabIndex];
return (
<div>
<p>{country.description}</p>
</div>
);
},
render () {
return (
<div style={styles.app}>
<div style={styles.tabs}>
{this.renderTabs()}
</div>
<div style={styles.tabPanels}>
{this.renderPanel()}
</div>
</div>
);
}
});
var styles = {};
styles.tab = { .. };
styles.activeTab = assign({}, styles.tab, { .. });
styles.tabPanels = {.. };
React.render(<App countries={DATA}/>, document.body);
To recap:- render UI
- attach handlers (e.g. click)
- modifies the state
- automatically re-render in a stateless fashion
Defining CSS style rules in JavaScript:
See bottom of previous snippet, expanded here:
..
var styles = {};
styles.tab = {
display: 'inline-block',
padding: 10,
margin: 10,
borderBottom: '4px solid',
borderBottomColor: '#ccc',
cursor: 'pointer'
};
// this is how you add styles
styles.activeTab = assign({}, styles.tab, {
borderBottomColor: '#000'
});
styles.tabPanels = {
padding: 10
};
React.render(<App countries={DATA}/>, document.body);
- Isn't it bad practice to call bind
on a function?Shows a standard JS example where you add
addEventListener
to a DOM element:el.addEventListener(someHandler.bind(this))
. You will not be able to remove this with removeEventListener
since bind
just created a new function.It's because of this side-effect, that this pattern is considered bad practice.
Under the hood React creates synthetic events. Events get delegated. So when you define an
onClick
attribute on a JSX element, it does not add a real click
attribute on the element. It just tell React to catch this click event, when the big, global click event happens. Now, in React you don't have to think about states in your app. You can make the
onClick
value conditional based on the state and React will remove it:<div onClick={shouldAddHandler ? this.handleTabClick.bind(this, index) : null}