State
So we've looked at passing variables into components, using props. But props cannot be changed once a component receives them, so what happens if we need to do that? Most apps are going to need to change what's shown on screen at some point, whether in response to the user clicking something, or data being loaded from the database or whatever.
This is where state variables come in. Unlike props, which are passed in from a parent component and cannot be changed, a state variable is defined within a component, and only that component can change the variable.
Overview
You may remember that we compared React components to the process of buying and driving a car. Props were the variables that could be set at the time of ordering the car - things like the colour, number of doors and so on. Once they're set, they can't be changed - if you want a 3-door car instead of a 5-door one, you'll need to order a new car; in the same way, once you've set the props for your component, you can't change them without 'ordering' a whole new component.
State variables are more like the variables that change once you're in the car and driving - things like the speed, which radio station you're listening to, and whether the headlights are on or off. These are all things that can be changed as we go by pressing a pedal or button. In the same way, a state variable can be changed by the component itself while that component is being shown on the screen - React doesn't need to redraw the whole component, and all other state variables are unaffected. React keeps track of state variables and automatically re-draws whichever parts of the component it needs to when one of them changes.
Declaring A State Variable
State variables are declared using React's useState hook (a 'hook' is a particular type of function in React; don't worry about it yet - for now it's enough to understand how to use it). Let's say we want to show a Button which starts out blue, and turns green when it's been clicked on. So, we want to define a variable which holds information on the current colour of the Button.
We have 2 options, I guess - we could have a boolean which is initially false and then turns to true when the Button is clicked, or we could have a string holding the name of the current colour. We'll choose the second option (the string) this time (though either will work fine), and we'll name our variable backgroundColour.
We define backgroundColour like this:
const [backgroundColour, setBackgroundColour] = React.useState('blue');
So what on earth is going on here? Firstly, look at the right hand side - React.useState is our 'hook' function (hook functions are a React Thing that we'll come back to); we're telling it to create a new state variable, and to give that variable the initial value 'blue'. Once it has done that, useState returns an array with 2 values; on the left hand side we're calling the first value 'backgroundColour', and the second value 'setBackgroundColour'.
NOTE: See array destructuring if you're wondering how this assignment works
But what's actually in backgroundColour and setBackgroundColour? Why are there 2 of them? Where did setBackgroundColour come from? We need to look behind the scenes here.
When we call useState, React does some setup for the new variable, and we can think of it as setting up 2 boxes.
Box 1 is where it stores the current value of the variable. So we can picture Box 1 as a cardboard box which, in our case, initially contains a piece of paper which says 'blue'. This is the box that we're calling backgroundColour. So when we use the backgroundColour variable, what we're really doing is lifting down Box 1, looking inside, and using whatever is written on the piece of paper in there.
Box 2 contains a function which can change the current value of backgroundColour. We can picture Box 2 (which we're calling setBackgroundColour - you can call it anything you like, but it's convention to call it setVariableName) as a cardboard box containing a small elf holding an eraser and a pencil*. When we use setBackgroundColour (eg with setBackgroundColour('green')), we're sending an order to the elf to reach into Box 1, rub out whatever's on the piece of paper, and write 'green' on there instead.
* Obviously this is not, technically, precisely what's happening
So, now we have a variable and a function which changes it... but why? Why bother doing this when we could just define something like let backgroundColour = 'blue' and then later on do backgroundColour = 'green'? The reason is that the setBackgroundColour function (the elf) does other things too; most notably, he lets React know that the change is being made, so React can track the change and update what's shown on the screen if it needs to.
NOTE: We can define as many state variables as we want. Changing one has no effect on the others, because React doesn't redraw the component when a state variable changes. On the other hand, changing a prop will cause all state variables to 'reset'
Example
Let's try to build the Button mentioned above - one that will change colour when it's clicked. Initially the Button is blue, but when it has been clicked, we want it to turn green. Here's how we create that:
If you haven't already done so, use create-react-app to generate a basic React app. Create a file called button.js in the same directory as App.js.
A basic Button component might look like this (copy this into button.js):
const Button = () => {
return <button>Click!</button>;
}
export default Button;
Then import it into App.js and use it, so App.js becomes:
import Button from './button';
const App = () => {
return <Button />;
};
export default App;
Run the app, and your browser should show a Button which says 'Click!' in the top left corner.
Now, we can change the colour of the Button with a bit of styling:
<button style={{ backgroundColor: 'blue', color: 'white' }}>Click!</button>
So now we have a blue Button.
Adding State
Next, we want the backgroundColor to change to green once the Button has been clicked, so we need backgroundColor to be a variable. Since it's being defined within a component (rather than being passed in from a parent component), we define a state variable, and use it for the Button colour:
import React from 'react';
const Button = () => {
const [backgroundColour, setBackgroundColour] = React.useState('blue');
return (
<button style={{ backgroundColor: backgroundColour, color: 'white' }}>Click!</button>
);
};
export default Button;
Notice that we have to import React in order to use useState.
Save this, and check the browser now - you should still be seeing a blue Button.
So far, this is exactly the same as if we had just done
const backgroundColour = 'blue';
but when we change the colour we're going to see the difference that comes when we use state.
Changing State
Finally, we want the backgroundColour to change when we click the Button. So, we need to add a click handler to the Button; the click handler is going to be a function which changes the value of backgroundColour to 'green'.
So, let's add the click handler function - put this immediately before the return:
const handleClick = () => {
setBackgroundColour('green');
}
This is just a standard JavaScript function, which updates the value of backgroundColour to green.
Now we want the function to be called when the Button is clicked - we do that by adding onClick to the Button:
<button
style={{ backgroundColor: colour, color: 'white' }}
onClick={handleClick}
>
Now, if you save this and look at the app in the browser, you'll still see the blue Button, but when you click it, it should change to green.
This is where state comes into its own - as soon as backgroundColour changes, the Button is updated on the screen to reflect that change.
Let's say we hadn't used state, but instead we'd just defined
let backgroundColour = 'blue';
and then in handleClick we'd done
backgroundColour = 'green';
In that case, clicking the Button would change the value of backgroundColour, but that would have no effect on the appearance of the Button - it would stay blue. That's because React only monitors state variables, and only redraws if one of them changes.
Comments
Post a Comment