props should be seen as data that is passed in to a component (think: function parameters). While you can modify this data, it may change out from under you if different props are passed into the component between renders.
To help with this, React provides a state system:
In React, you simply update a component's state, and then render a new UI based on this new state. React takes care of updating the DOM for you in the most efficient way.
Unlike props, you cannot pass state into a component. It is designed to be entirely internal to a component. Think of it as variables that are local to the component's imaginary closure.
The MenuItem
component we've made is a Pure Functional Component, which
means it cannot have state. React provides another method of creating components
that is more verbose, but which supports state. Confusingly it's done by calling
React.createClass
:
let MyComponent = React.createClass({
render: function() {
// ...
}
})
Every component of this form must have a render
method. This render
method
is where we return our React.createElement
or JSX like before. React will use
this as the thing to render.
To convert our MenuItem
component, we could do:
let MenuItem = React.createClass({
render: function() {
return <li>Menu Item</li>
}
})
But now we're missing our props, the values passed into the component. React
takes care of this by providing props as this.props
.
Here is a hypothetical Warning component:
let Warning = React.createClass({
render: function() {
let style = {
backgroundColor: this.props.isError ? 'red' : 'yellow'
}
return <div style={style}>{this.props.message}</div>
}
})
And to set default prop values, we can use the getDefaultProps
function:
let Warning = React.createClass({
getDefaultProps: function() {
return {
isError: false,
message: 'Oops! Something went wrong!'
}
},
render: function() {
let style = {
backgroundColor: this.props.isError ? 'red' : 'yellow'
}
return <div style={style}>{this.props.message}</div>
}
})
Convert the Pure Function Component MenuItem
to a Stateful Component as
demonstrated above.
Check to make sure you conversion works by rebuilding (npm run build
),
restarting your server (npm start
), and refreshing the webpage.
Hints:
- Only
src/js/menu-item.js
has to be modified - Be sure to setup a default prop value for
isActive
- See the
React.createElement
docs
As with default props, components can have default state. A common pattern
(although one which is not always
recommended)
is to base initial state on the passed in props thanks to the getInitialState
method
let Warning = React.createClass({
getInitialState: function() {
return {
// Default to showing the details when it's an error, but only show
// summary when it's a warning
detailsVisible: this.props.isError
}
},
// ...
})
You can access state inside any component via this.state
, and you can update
it by calling this.setState()
with an object of the state changes. For
example:
let Warning = React.createClass({
// ...
render: function() {
let style = {
backgroundColor: this.props.isError ? 'red' : 'yellow'
}
return (
<div style={style}>
{/* Always show the message */}
{this.props.message}
{/* Only show details if `this.state.displayDetails` is truthy */}
{this.state.displayDetails ? (
<p>
{this.props.details}
</p>
) : false}
</div>
)
}
})
Update MenuItem
to set initial state for subMenuVisible
based on if the
props indicate the item is Active (ie; show sub menus when set to active). Then
use the state in the render()
method in place of this.props
to render a
dummy sub menu item.
Awesome, but now how do we alter the state?
Let's start by defining under what circumstances you would want to change the state. Let's say it's based on some event. React comes with a bunch of events we can set as props.
Changing some state on Click sounds like a good approach. Therefore, the one we
want is onClick
:
let MyComponent = React.createClass({
render: function() {
return <div onClick={}>This is awesome!</div>
}
})
And we want to pass in a function to that prop so we can change the state:
let MyComponent = React.createClass({
handleClick: function() {
console.log('clicked')
},
render: function() {
return <div onClick={this.handleClick}>This is awesome!</div>
}
})
Note: this
inside a component will always refer to the component itself.
Great, now we're capturing clicks on that div and outputting clicked
to the
console when they occur. To set the state, we can use setState
:
let MyComponent = React.createClass({
handleClick: function() {
this.setState({
isAwesome: !this.state.isAwesome // toggle
})
},
// ...
})
Note: When you call setState()
, the component's render()
will automatically
be executed for you.
Since we've been building a menu, we probably want to show some sort of hover state.
Add functionality to MenuItem
which makes the text green on hover, and turns
it back into black when not hovering.
Hints:
- You'll need 2 event
listeners
- one for when the mouse moves over the element, and one for when the mouse is no longer over the element.
- Use
this.setState
to set anisHovering
boolean, then use that in yourrender()
method
Add functionality to MenuItem
so that when clicked, the sub menu visibility is
toggled.
Hints:
- You'll need a click event listener.
An important point to note about state and props: They are unique to each component instance. This allows you to reuse the same component definition multiple times, and have them manage all their own state. By this method of encapsulating the state and functionality, we're building truly reusable components.