Stateful Hooks page
Learn how to use React hooks to manage the state of a component.
What are stateful hooks?
Stateful hooks are React's way of managing the data that a functional component needs to remember. The most widely used stateful hook, useState
, streamlines the process of both using and updating individual state values in a straightforward way.
Another stateful hook, useRef
, gives component code access to the underlying DOM elements rendered from the JSX.
Let's take a look at each hook and see what they can do.
useState
useState
accepts a single parameter and returns an array with two elements. The argument to useState
is the default value of the state
that you are creating. The first element of the return value is the state value itself; this is the value you would render inside the JSX. The second element is a setter function, which, when called, can be used to update the state value and trigger a re-render.
Here's an example of a React component that uses state to power an input element.
function Hello() {
const [inputValue, setInputValue] = React.useState('');
return (
<input
value={inputValue.toUpperCase()}
onChange={(e) => setInputValue(e.target.value)}
/>
);
}
ReactDOM.render(<Hello />, document.getElementById('root'));
Above is a simple example of using state to control an input
element. The starting value of inputValue
is ''
because it is the argument to React.useState
. If we were to call setInputValue('hello')
, then inputValue
would change to hello
and the component would re-render.
Note also that the input box always displays the uppercase
value of the text. This is because when we display the text value (when we pass the value
prop to the input
), we uppercase the string.
This process is similar no matter what component you're building. The basic pattern is:
- Get the value and setter from
useState
. - Use the value as needed
- Call the setter to change the value and trigger an update
Important: When using objects or arrays, you must provide a new state object when using the setter, rather than changing the values within the object. The data created by
useState
is immutable and will always replace any manual changes.For example, using a state object that is an array, make sure never to use
.push()
or.pop()
directly on it. Instead, use methods like.concat()
and.filter()
which create new arrays, and pass those values into the setter.Wrong
function Hello() { const [values, setValues] = React.useState([]); return ( <> {values} <button onChange={(e) => { values.push('click'); setValues(values); }} > Click me </button> </> ); } ReactDOM.render(<Hello />, document.getElementById('root'));
@highlight 9
Right
function Hello() { const [values, setValues] = React.useState([]); return ( <> {values} <button onChange={(e) => setValues(values.concat('click'))}> Click me </button> </> ); } ReactDOM.render(<Hello />, document.getElementById('root'));
@highlight 7
useRef
Just like useState
, useRef
is a data store, keeping track of values the component is interested in. In this case however, useRef
helps store DOM references instead of state data.
DOM references give your component access to the underlying DOM elements that your JSX creates. These references can then be used to make more specific changes to the DOM or provide access to external libraries. To get to the value of a ref, it is always located in the .current
property. It's important to note that updating refs does not cause updates like modifying the state does.
Let's take a look at useRef
in action:
function Hello() {
const nameRef = React.useRef();
return (
<>
<label>
Name:
<input placeholder="name" type="text" ref={nameRef} />
</label>
<button onClick={() => nameRef.current.focus()}>Focus Name Input</button>
</>
);
}
ReactDOM.render(<Hello />, document.getElementById('root'));
In the code above we're doing a couple things:
- Creating a ref that will reference the input field.
- Use that input ref so that button onClick handler to
.focus()
on the underlying DOM element.
This strategy is useful when you need to interface with older libraries that require you have the reference to the actual DOM element instance.
Exercise
Let's use our stateful hooks knowledge to make our Tic-Tac-Toe game more functional!
The problem
✏️ Modify the Board
component so that it keeps track of the current state of the game and players using hooks.
- store the current board in state, represented as an array of strings (ex.
["", "x", "o" ...]
) - store the current player in state, with a boolean called
isXTurn
- Fill out the
handleSquareClick
function so that it updates the board and changes the current player when the user clicks aSquare
.
const squareStyling = {
width: '200px',
height: '200px',
border: '1px solid black',
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: '6em',
color: 'black',
};
const boardStyling = {
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'row',
justifyContent: 'flex-start',
width: '600px',
height: '600px',
boxShadow: '0px 3px 8px 0 rgba(0, 0, 0, 0.1)',
boxSizing: 'border-box',
};
function Square({ onClick, symbol, id }) {
return (
<div id={id} onClick={onClick} style={squareStyling}>
{symbol}
</div>
);
}
function Board() {
const board = ['', '', '', '', '', '', '', '', ''];
// store the board in state
// store the current player in state
function handleSquareClick(square) {
// update the board
// update the current player
}
return (
<div style={boardStyling}>
{board.map((symbol, index) => (
<Square
key={index}
symbol={symbol}
onClick={() => handleSquareClick(index)}
/>
))}
</div>
);
}
ReactDOM.render(<Board />, document.getElementById('root'));
Solution
Click to see the solution
const squareStyling = {
width: '200px',
height: '200px',
border: '1px solid black',
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: '6em',
color: 'black',
};
const boardStyling = {
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'row',
justifyContent: 'flex-start',
width: '600px',
height: '600px',
boxShadow: '0px 3px 8px 0 rgba(0, 0, 0, 0.1)',
boxSizing: 'border-box',
};
function Square({ onClick, symbol, id }) {
return (
<div id={id} onClick={onClick} style={squareStyling}>
{symbol}
</div>
);
}
function Board() {
const [board, setBoard] = React.useState([
'',
'',
'',
'',
'',
'',
'',
'',
'',
]);
const [isXTurn, setIsXTurn] = React.useState(true);
function handleSquareClick(squareIndex) {
if (board[squareIndex]) {
return;
}
const newBoard = [...board];
newBoard[squareIndex] = isXTurn ? 'X' : 'O';
setBoard(newBoard);
setIsXTurn(!isXTurn);
}
return (
<div style={boardStyling}>
{board.map((symbol, index) => (
<Square
key={index}
symbol={symbol}
onClick={() => handleSquareClick(index)}
/>
))}
</div>
);
}
ReactDOM.render(<Board />, document.getElementById('root'));
Next Steps
✏️ Head over to the next lesson to learn how React manages browser inputs.