Managing Complex State page
Manage component and application state with hooks.
What is state?
In React, props
are the "arguments" that are passed to our component. Alternatively, state
can be conceptualized as the "internal variables" of our function. But what makes state
different from normal internal variables? state
is given superpowers! Anytime state
is changed (similarly with props), the component magically re-renders itself.
Use state
for internal variables, which when changed, should re-render the component. Examples include the value of a text box, whether or not a modal is open, and the current tab selection.
Using functional components, the useState()
hook provides a value and setter capable of holding any data type. This allows us to hold simple variables such as:
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(true);
In the example above, count
starts at value 0 setCount
is a function that allows us to update the count.
Similarly, flag
takes on the starting value of true
, but if we need to change that we must use the setFlag
function.
useState
can hold more complex data.
const [ viewModel, setViewModel ] = useState({
showModal: false,
currentUser: {
name: 'Mike Myers'
id: 42
},
highlightItems: [1, 12, 15, 34, 66]
})
Scope of State
useState
is scoped to the component it is declared in. For example, this component stores a simple string savedText
and displays its value as the user makes changes to a text input.
import React, { useState } from 'react';
function DisplayComponent() {
const [savedText, setSavedText] = useState('');
function onSetText(event) {
setSavedText(event.target.value);
}
return (
<div>
<h1>You entered: {savedText}</h1>
<input type="text" value={savedText} onChange={onSetText} />
</div>
);
}
Now imagine our layout requires a button to confirm the savedText
change, but the button appears in a different part of the page! How will we modify the state in a different component?
Lucky for us, values and setters from useState
are standard JavaScript variables and can be passed as props
. We can create a WrapperContainer
component to hold the state where both Display and Button components have access. Consider the following structure:
function WrapperContainer() {
//TODO: setup the state variables
return (
<div>
<div className="layout-left">
<DisplayComponent />
</div>
<div className="layout-right">
<ButtonComponent />
</div>
</div>
);
}
function DisplayComponent() {
return (
<div>
<h1>You entered:</h1>
<input type="text" />
</div>
);
}
function ButtonComponent() {
return <button type="button">Set Message</button>;
}
Let’s create two state items in the WrapperContainer
.
savedText
as we defined in the earlier example will hold the set message.unsavedText
will keep the inputs typed by the user until they are saved
Pass these and the setUnsavedText
setter as props
to the DisplayComponent
.
function WrapperContainer() {
// define the savedText to be shown
const [savedText, setSavedText] = useState('');
// create a holder for unsaved text
const [unsavedText, setUnsavedText] = useState('');
return (
<div>
<div className="layout-left">
<DisplayComponent
unsavedText={unsavedText}
setUnsavedText={setUnsavedText}
savedText={savedText}
/>
</div>
<div className="layout-right">
<ButtonComponent />
</div>
</div>
);
}
Using this data in the DisplayComponent
is simple. Use the props to render the component and call setUnsavedText
on changes.
function DisplayComponent({ unsavedText, setUnsavedText, savedText }) {
function onSetText(event) {
//notice we use the passed in setter defined in the WrapperContainer
setUnsavedText(event.target.value);
}
return (
<div>
<h1>You entered: {savedText}</h1>
{/* Use unsavedText rather than savedText */}
<input type="text" value={unsavedText} onChange={onSetText} />
</div>
);
}
Next, let’s wire up that button. We’ll define an onButtonClick
callback to handle changes to the state variables. Pass the callback down to ButtonComponent
.
function WrapperContainer() {
// define the savedText to be shown
const [savedText, setSavedText] = useState('');
// create a holder for unsaved text
const [unsavedText, setUnsavedText] = useState('');
function onButtonClick() {
// save the new savedText
setSavedText(unsavedText);
// reset the text input
setUnsavedText('');
}
return (
<div>
<div className="layout-left">
<DisplayComponent
unsavedText={unsavedText}
setUnsavedText={setUnsavedText}
savedText={savedText}
/>
</div>
<div className="layout-right">
<ButtonComponent onButtonClick={onButtonClick} />
</div>
</div>
);
}
Lastly, update the ButtonComponent
to use our new callback.
function ButtonComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Set Message
</button>
);
}
Completed Example
Click to see the solution
function WrapperContainer() {
// define the savedText to be shown
const [savedText, setSavedText] = React.useState('');
// create a holder for unsaved text
const [unsavedText, setUnsavedText] = React.useState('');
function onButtonClick() {
// save the new savedText
setSavedText(unsavedText);
// reset the text input
setUnsavedText('');
}
return (
<div>
<div className="layout-left">
<DisplayComponent
unsavedText={unsavedText}
setUnsavedText={setUnsavedText}
savedText={savedText}
/>
</div>
<div className="layout-right">
<ButtonComponent onButtonClick={onButtonClick} />
</div>
</div>
);
}
function DisplayComponent({ unsavedText, setUnsavedText, savedText }) {
function onSetText(event) {
//notice we use the passed in setter defined in the WrapperContainer
setUnsavedText(event.target.value);
}
return (
<div>
<h1>You entered: {savedText}</h1>
{/* Use unsavedText rather than savedText */}
<input type="text" value={unsavedText} onChange={onSetText} />
</div>
);
}
function ButtonComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Set Message
</button>
);
}
ReactDOM.render(<WrapperContainer />, document.getElementById('root'));
What data should be kept in React state?
There is no "right" answer for what data should be stored in state. Applications with simple API requirements may keep everything in state objects. Other apps may opt to store nearly everything in a separate global state management library such as Redux or Apollo.
The next lesson will discuss a purely React method of maintaining and exposing state accross your application.
Next steps
✏️ Head over to the next lesson to get a more detailed picture on how and when you can use context.