This article is part of a React Family series inspired by my conversation with Jason Lengstorf on this episode of Learn with Jason.
Cover photo by Tengyart on Unsplash
In this example, we have three React components. One, <Mom />
is, of course, the parent component. The others are children of <Mom />
:
-
<Toddler />
and -
<Teen />
.
1 const Mom = () => {
2
3 return (
4 <div>
5 <Toddler />
6 <Teen />
7 </div>
8 )
9 }
These components don't do or show anything just yet, but using this small React family, we'll see how state
can be set and passed between family members components.
State at a High Level
State
, like props
, is an object containing information used to determine what's rendered by your React app. This could be anything, from colors to a tally to whether a modal should currently be displayed on the page or hidden from view. React state
, in this example, is going to be the emotional state of our components.
Unlike props
, state
is managed within the component it lives on. By contrast, props
is set and managed outside of the component, then passed into the component using it.
State should always live on the highest-order component that’s involved in its use. There are two reasons for this:
- State-setting functions can only be used to set
state
for either (1) the component that holds the function or for (2) one of its parents. A parent, or higher-order component, cannot have a setstate
that lives on a child component. -
State
can be passed from parent to child (via props), information about state can be passed from child to parent (via state-setting functions), but information can only be passed from sibling-to-sibling by going through the parent (via a combination of state-setting functions and props).
We’ll explore these in further detail, but for now just understand that in our case state will need to live on <Mom />
because she is the highest-order component involved in using our state
.
When a React component is rendered, it uses state
's initial value. This value could be any Javascript data type or structural type including null, undefined, 0, or just a blank value (like an empty string). In our example, our parent component <Mom />
has an initial state of string ‘happy’. We set the initial state to happy
using the useState
hook and render it in the DOM using JSX on line 9 below.
1 const Mom = () => {
2
3 const [emotion, setEmotion] = useState('happy')
4
5 return (
6 <div>
7 <Toddler />
8 <Teen />
9 {emotion} //happy
10 </div>
11 )
12 }
Setting State
Components setting their own state
Just like your emotional state, React state
can be changed and maintained by a trigger (a function). In class components, these functions would be called setState functions. We’ll be using React Hooks, so our function can be called whatever we want to call it. Common convention is to call it set + WhateverTheFunctionDoes
. Anytime state
is changed, the component is re-rendered.
<Mom />
can hold these functions to update her own state, as below. handleMeditation()
changes <Mom />
’s state
to ‘calm’
. When a user clicks the button we've added to lines 13-15, setEmotion
is triggered and our state
of emotion
is changed to calm.
1 const Mom = () => {
2
3 const [emotion, setEmotion] = useState('happy')
4
5 const handleMeditation = () => {
6 setEmotion('calm')
7 }
8
9 return (
10 <div>
11 <Toddler />
12 <Teen />
13 <button onClick={handleMeditation}>
14 {emotion} //changes to 'calm' once button is clicked
15 </button>
16 </div>
17 )
18 }
Passing information from parent to child
From here, <Mom />
can pass her emotional state on to her children 😃 as props. <Toddler />
and <Teen />
will both get passed the initial props of happy
. Any time <Mom />
’s state changes, the emotional state of the children will be updated to match <Mom />
’s.
1 const Mom = () => {
2
3 const [emotion, setEmotion] = useState('happy')
4
5 const handleMeditation = () => {
6 setEmotion('calm')
7 }
8
9 return (
10 <div>
//initial props value of 'happy' changes to 'calm' in Toddler and Teen once button is clicked
11 <Toddler emotion={emotion} />
12 <Teen emotion={emotion} />
13 <button onClick={handleMeditation}>
14 {emotion} //changes to 'calm' once button is clicked
15 </button>
16 </div>
17 )
18 }
Passing information from child to parent
State-setting functions can also be passed down to children as props
. If a child has access to any trigger - or function - that is paired with <Mom />
’s state, the child thus has the ability to manipulate <Mom />
’s - or the parent component’s - state
.
Let’s say that <Mom />
has a different handler function called handleTemperTantrum()
. <Mom />
passes this function to child <Toddler />
as a prop:
1 const Mom = () => {
2
3 const [emotion, setEmotion] = useState('happy')
4
5 const handleTemperTantrum = () => {
6 setEmotion('annoyed')
7 }
8
9 return (
10 <div>
11 <Toddler handleTemperTantrum={handleTemperTantrum} />
12 <Teen />
13 </div>
14 )
15 }
Now, <Toddler />
has the ability to manipulate update and manage <Mom />
’s state on its own! <Toddler />
uses this function to change <Mom />
’s state to 'annoyed' any time the button on lines 4-6 is clicked.
Toddler Component:
1 const Toddler = ({handleTemperTantrum}) => {
2
3 return (
4 <button onClick={handleTemperTantrum}>
5
6 </button>
7 )
8 }
This button does not actually display anything except an empty button, though. Since the actual state
of emotion
still lives on <Mom />
, we'll need to pass emotion
down to her child. In fact, since parents can pass state down to any children as props
, we can pass annoyed
to both <Toddler />
and <Teen />
as props for them to use.
Mom Component:
1 const Mom = () => {
2
3 const [emotion, setEmotion] = useState('happy')
4
5 const handleTemperTantrum = () => {
6 setEmotion('annoyed')
7 }
8
9 return (
10 <div>
11 <Toddler handleTemperTantrum={handleTemperTantrum} emotion={emotion} />
12 <Teen emotion={emotion} />
13 </div>
14 )
15 }
Toddler Component:
1 const Toddler = ({handleTemperTantrum, emotion}) => {
2
3 return (
4 <button onClick={handleTemperTantrum}>
5 {emotion} //now this will say 'calm', or will change to 'annoyed' once this button is clicked
6 </button>
7 )
8 }
Any time <Mom />
’s state is updated, <Toddler />
s and <Teen />
s emotional states are updated accordingly.
State-setting functions can either live on the component that the state lives on, or on one of its children.
Passing information from sibling to sibling
Siblings can pass state between one another, but they have to do it through their common parent. Just as we did in the last example, <Toddler />
can pass state to <Teen />
using this workflow:
-
<Toddler />
has thesetTemperTantrum()
function, which manages and updates thestate
that lives on<Mom />
-
<Toddler />
sets<Mom />
’s state toannoyed
-
<Mom />
passesannoyed
as a prop to<Teen />
-
<Teen />
accepts emotion as a prop.
Latest comments (4)
Great article. There are some typos though.
A function that updates a state cannot be invoked inside of the render function or in the return block of a functional component, it will lead to infinite re-renders. You should instead pass the reference of the function.
So it is
onClick={handleMeditation}
instead ofonClick={handleMeditation()}
.Also when passing props to functional components, to specify each props name you need to destructure.
So it's
const Toddler=({handleTemperTantrum, emotion})=>{}
instead ofconst Toddler=(handleTemperTantrum, emotion)=>{}
Great catches! Normally I do a better job proofreading these before publishing :D fixed - thank you!
Loved this whole article with the names of the components, functions and state 😂
Great explanation with actionable examples of how to use and pass state! Awesome post, Shaundai! 😃
Thanks Rafael for reading 💖