State
Occasionally, a component will need to keep track of some internal state in addition to the external, read-only, properties that are passed into it. State is necessarily internal to the component and, generally, exclusively tied to some visual display option (for instance, is the component visually expanded or collapsed).
Much in the same way that a component instance can access external properties via this.props
, a component instance can access its internal state using this.state
. Using internal state, we could optionally show parts of NewsItem
only when that item is in an expanded state:
render() { let body = null; if (this.state.expanded) { body = ( <div> <Byline /> <Description /> </div> ); } return ( <div className="news-item" onClick={this.onClick} > <Image /> <Title highlighted > {this.props.titleText} </Title> {body} </div> ); }
We can see now that the body
variable will only be defined if the internal state is expanded. Another thing we can see here is that a <div>
element has been added around the description
and byline
. The reason we do this is because JSX elements must have a single root node in order to return them or store them in a variable. Alternatively, we could have stored each element in its own variable:
render() { let byline = null; let description = null; if (this.state.expanded) { byline = <Byline />; description = <Description />; } return ( <div className="news-item" onClick={this.onClick} > <Image /> <Title highlighted > {this.props.titleText} </Title> {byline} {description} </div> ); }
While this code is completely valid, we can make it even better by splitting out conditional rendering into a separate method:
renderBody() { if (this.state.expanded) { return ( <div> <Byline /> <Description /> </div> ); } return null; }
Then, we can use this helper
method within our main render()
method in order to make things a bit clearer:
render() {
return (
<div
className="news-item"
onClick={this.onClick}
>
<Image />
<Title
highlighted
>
{this.props.titleText}
</Title>
{this.renderBody()}
</div>
);
}
We've now seen how to use internal state to render things conditionally, but we have not yet seen how that state
is defined or how it is modified. In React, we can specify the initial values of internal state by assigning them in the constructor of the component. The component's initial state
, much like its default properties, should be a JavaScript object:
constructor(props) { super(props); this.state = { expanded: false }; this.onClick = this.onClick.bind(this); }
This method describes the initial state of a component, but it does not provide us with any means to update that state
. In order to update the state of a component, we can use a React component's setState
method to assign, or reassign, any internal state
value.
Typically, updating state happens as a response to some user input or user event. In the last section, we learned how to define methods that respond to these user events, such as clicks, and how to attach these event listeners to the appropriate React element. Let's modify our onClick
event handler to change the expanded state of our component instead of simply alerting:
onClick() {
this.setState({
expanded: !this.state.expanded
});
}
When we use setState
in this way, React will notice that the internal state has changed, and this will trigger a new rendering using the new internal state. For this reason, we should never manipulate the state
of a component directly:
//Do not do this this.state.expanded = false;
If we change the internal state directly, React's rendering engine will not become aware of it and the component we see on our page will differ from the one in JavaScript. The same goes for props
; they are external and should only be changed as a result of new values being passed in through JSX:
//Also don't do this this.props.titleText = 'Hello World!';
Now that we've demonstrated how to use internal state
to display something conditionally, how to initialize state by setting it in the constructor method, and how to modify internal state in response to some user event using setState
, let's look at all of this in context in our NewsItem
component:
import React, { Component, PropTypes } from 'react'; import Title from './Title'; export default class NewsItem extends Component { constructor(props) { super(props); this.state = { expanded: false }; this.onClick = this.onClick.bind(this); } onClick() { this.setState({ expanded: !this.state.expanded }); } renderBody() { if (this.state.expanded) { return ( <div> <Byline /> <Description /> </div> ); } return null; } render() { return ( <div className="news-item" onClick={this.onClick} > <Image /> <Title highlighted > {this.props.titleText} </Title> {this.renderBody()} </div> ); } } NewsItem.propTypes = { titleText: PropTypes.string.isRequired };
Now we have a component for our news item that starts out collapsed (not expanded) and not showing the description or byline, but when the user clicks on the news item, it expands to show the two previously hidden elements.