We've already seen how to pass props to a React component in order to give it some starting values, but what happens then – what if the component needs to update those props because the user clicked a button, or entered some text, or, or, or…?
React has a solution and it's called state. State looks and works just like props, with one exception: while props are read-only by their owning component, any component can change its own state or even a different component's state if it wants to.
It's important that you understand storing state is entirely optional, and indeed it's preferable to avoid it where possible. You see, if you subtract state from a component, you get what is called a "stateless component" or "dumb component", which is a component that will always do the same thing when given the same set of props. Stateless components are easier to understand and easier to test, which is always a good thing.
Still, you will need to use state at some point, so let's give it a try. Right now we output a new name and place every time the component is rendered, but we could modify our class so that we calculate the name and place when it's created then never again – we're making it random when the page loads, then part of the component state thereafter.
To do this you need to learn three new things:
constructor(),
super() and the
this.state property.
The
constructor() method is automatically called when an object is created, and gives us chance to do any initialization work required to make the object useful. This only gets called once for each object, and you never call it directly.
The
super() method is a special method that means "call this same method on whichever class I inherited from." If you remember, our Detail component builds on
React.Component – that's what this line means:
class Detail extends React.Component {
This means our Detail component gets all the functionality provided by its parent class,
React.Component. It also means that it's good practise to let
React.Component do its own initialization if it needs to, which is what
super() does: it tells the parent class to go ahead and run the same method on itself before we can continue.
Finally, the
this.state property. This is undefined by default (i.e., it doesn't exist), but you can create it to be a dictionary of items that contain any valid ES6 data.
Putting those three things together we're going to 1) write a constructor for the Detail component that 2) calls
super() so that
React.Component can do any initializing it needs to, then 3) sets
this.state to be a value containing a name and a country of our random person.
Here's the code to do just that – put this just above the
buttonClicked() method that exists right now:
src/pages/Detail.js
constructor(props) { super(props); this.state = { name: chance.first(), country: chance.country({ full: true }) }; }
I did sneak one extra little thing in there, which is that
constructor() takes the component's props as its only input parameter. This must get passed on with the
super() so that the parent class can act on those props as needed.
So, now that the name and country of our person are being set up front, we can adjust our
render() method to use them:
src/pages/Detail.js
render() { return (<div> <p>Hello, {this.state.name}.</p> <p>You're from {this.state.country}.</p> <button onClick={this.buttonClicked.bind(this)}>Meet Someone New</button> </div>); }
The resulting page might look the same, but if you click the button you'll see that nothing happens – the name and country being shown never change, even though it's all working correctly. The reason for this is because the name and country values are set up when the component is created, not when it's rendered, so it doesn't matter how many times
forceUpdate() gets called: the output won't!