3 - Fullstack Part1 - Component State, Event Handlers
3 - Fullstack Part1 - Component State, Event Handlers
return (
<div>
<h1>Greetings</h1>
<Hello name="Maya" age={26 + 10} />
<Hello name={name} age={age} />
</div>
)
}
Let's expand our Hello component so that it guesses the year of birth of the person being greeted:
https://fullstackopen.com/en/part1/component_state_event_handlers 1/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
return (
<div>
<p>
Hello {props.name}, you are {props.age} years old
</p>
<p>So you were probably born in {bornYear()}</p>
</div>
)
}
The logic for guessing the year of birth is separated into a function of its own that is called when the
component is rendered.
The person's age does not have to be passed as a parameter to the function, since it can directly
access all props that are passed to the component.
If we examine our current code closely, we'll notice that the helper function is defined inside of
another function that defines the behavior of our component. In Java programming, defining a
function inside another one is complex and cumbersome, so not all that common. In JavaScript,
however, defining functions within functions is a commonly-used technique.
Destructuring
Before we move forward, we will take a look at a small but useful feature of the JavaScript language
that was added in the ES6 specification, that allows us to destructure values from objects and arrays
upon assignment.
In our previous code, we had to reference the data passed to our component as props.name and
props.age . Of these two expressions, we had to repeat props.age twice in our code.
props = { copy
name: 'Arto Hellas',
age: 35,
}
we can streamline our component by assigning the values of the properties directly into two
variables name and age which we can then use in our code:
https://fullstackopen.com/en/part1/component_state_event_handlers 2/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
return (
<div>
<p>Hello {name}, you are {age} years old</p>
<p>So you were probably born in {bornYear()}</p>
</div>
)
}
Note that we've also utilized the more compact syntax for arrow functions when defining the
bornYear function. As mentioned earlier, if an arrow function consists of a single expression, then
the function body does not need to be written inside of curly braces. In this more compact form, the
function simply returns the result of the single expression.
Destructuring makes the assignment of variables even easier since we can use it to extract and
gather the values of an object's properties into separate variables:
return (
<div>
<p>Hello {name}, you are {age} years old</p>
<p>So you were probably born in {bornYear()}</p>
</div>
)
}
props = { copy
name: 'Arto Hellas',
age: 35,
}
the expression const { name, age } = props assigns the values 'Arto Hellas' to name and
35 to age .
https://fullstackopen.com/en/part1/component_state_event_handlers 3/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
return (
<div>
<p>
Hello {name}, you are {age} years old
</p>
<p>So you were probably born in {bornYear()}</p>
</div>
)
}
The props that are passed to the component are now directly destructured into the variables, name
and age .
This means that instead of assigning the entire props object into a variable called props and then
assigning its properties to the variables name and age
we assign the values of the properties directly to variables by destructuring the props object that is
passed to the component function as a parameter:
Page re-rendering
So far all of our applications have been such that their appearance remains the same after the initial
rendering. What if we wanted to create a counter where the value increased as a function of time or
at the click of a button?
https://fullstackopen.com/en/part1/component_state_event_handlers 4/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
let counter = 1
ReactDOM.createRoot(document.getElementById('root')).render(
<App counter={counter} />
)
The App component is given the value of the counter via the counter prop. This component
renders the value to the screen. What happens when the value of counter changes? Even if we
were to add the following
counter += 1 copy
the component won't re-render. We can get the component to re-render by calling the render
method a second time, e.g. in the following way:
refresh()
counter += 1
refresh()
counter += 1
refresh()
The re-rendering command has been wrapped inside of the refresh function to cut down on the
amount of copy-pasted code.
Now the component renders three times, first with the value 1, then 2, and finally 3. However, values
1 and 2 are displayed on the screen for such a short amount of time that they can't be noticed.
We can implement slightly more interesting functionality by re-rendering and incrementing the
counter every second by using setInterval :
https://fullstackopen.com/en/part1/component_state_event_handlers 5/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
counter += 1
}, 1000)
Making repeated calls to the render method is not the recommended way to re-render
components. Next, we'll introduce a better way of accomplishing this effect.
Stateful component
All of our components up till now have been simple in the sense that they have not contained any
state that could change during the lifecycle of the component.
Next, let's add state to our application's App component with the help of React's state hook .
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
setTimeout(
() => setCounter(counter + 1),
1000
)
return (
<div>{counter}</div>
)
}
The function body that defines the component begins with the function call:
https://fullstackopen.com/en/part1/component_state_event_handlers 6/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
The function call adds state to the component and renders it initialized with the value of zero. The
function returns an array that contains two items. We assign the items to the variables counter
and setCounter by using the destructuring assignment syntax shown earlier.
The counter variable is assigned the initial value of state which is zero. The variable
setCounter is assigned a function that will be used to modify the state.
The application calls the setTimeout function and passes it two parameters: a function to increment
the counter state and a timeout of one second:
setTimeout( copy
() => setCounter(counter + 1),
1000
)
The function passed as the first parameter to the setTimeout function is invoked one second after
calling the setTimeout function
When the state modifying function setCounter is called, React re-renders the component which
means that the function body of the component function gets re-executed:
() => { copy
const [ counter, setCounter ] = useState(0)
setTimeout(
() => setCounter(counter + 1),
1000
)
return (
<div>{counter}</div>
)
}
The second time the component function is executed it calls the useState function and returns
the new value of the state: 1. Executing the function body again also makes a new function call to
setTimeout , which executes the one-second timeout and increments the counter state again.
Because the value of the counter variable is 1, incrementing the value by 1 is essentially the same
as an expression setting the value of counter to 2.
https://fullstackopen.com/en/part1/component_state_event_handlers 7/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
Every time the setCounter modifies the state it causes the component to re-render. The value of
the state will be incremented again after one second, and this will continue to repeat for as long as
the application is running.
If the component doesn't render when you think it should, or if it renders at the "wrong time", you
can debug the application by logging the values of the component's variables to the console. If we
make the following additions to our code:
setTimeout(
() => setCounter(counter + 1),
1000
)
console.log('rendering...', counter)
return (
<div>{counter}</div>
)
}
It's easy to follow and track the calls made to the App component's render function:
https://fullstackopen.com/en/part1/component_state_event_handlers 8/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
Was your browser console open? If it wasn't, then promise that this was the last time you need to be
reminded about it.
Event handling
We have already mentioned event handlers that are registered to be called when specific events
occur a few times in part 0 . A user's interaction with the different elements of a web page can cause
a collection of various kinds of events to be triggered.
Let's change the application so that increasing the counter happens when a user clicks a button,
which is implemented with the button element.
Button elements support so-called mouse events , of which click is the most common event. The
click event on a button can also be triggered with the keyboard or a touch screen despite the name
mouse event.
In React, registering an event handler function to the click event happens like this:
return (
<div>
<div>{counter}</div>
<button onClick={handleClick}>
plus
</button>
</div>
)
}
We set the value of the button's onClick attribute to be a reference to the handleClick function
defined in the code.
Now every click of the plus button causes the handleClick function to be called, meaning that
every click event will log a clicked message to the browser console.
The event handler function can also be defined directly in the value assignment of the onClick-
attribute:
return (
<div>
<div>{counter}</div>
<button onClick={() => console.log('clicked')}>
https://fullstackopen.com/en/part1/component_state_event_handlers 9/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
plus
</button>
</div>
)
}
we achieve the desired behavior, meaning that the value of counter is increased by one and the
component gets re-rendered.
return (
<div>
<div>{counter}</div>
<button onClick={() => setCounter(counter + 1)}>
plus
</button>
<button onClick={() => setCounter(0)}>
zero
</button>
</div>
)
}
We define the event handlers for our buttons where we declare their onClick attributes:
https://fullstackopen.com/en/part1/component_state_event_handlers 10/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
</button>
What's going on? An event handler is supposed to be either a function or a function reference, and
when we write:
the event handler is actually a function call. In many situations this is ok, but not in this particular
situation. In the beginning, the value of the counter variable is 0. When React renders the component
for the first time, it executes the function call setCounter(0+1) , and changes the value of the
component's state to 1. This will cause the component to be re-rendered, React will execute the
setCounter function call again, and the state will change leading to another re-render...
Now the button's attribute which defines what happens when the button is clicked - onClick - has the
value () => setCounter(counter + 1) . The setCounter function is called only when a user
clicks the button.
https://fullstackopen.com/en/part1/component_state_event_handlers 11/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
Usually defining event handlers within JSX-templates is not a good idea. Here it's ok, because our
event handlers are so simple.
return (
<div>
<div>{counter}</div>
<button onClick={increaseByOne}>
plus
</button>
<button onClick={setToZero}>
zero
</button>
</div>
)
}
Here, the event handlers have been defined correctly. The value of the onClick attribute is a variable
containing a reference to a function:
It's recommended to write React components that are small and reusable across the application and
even across projects. Let's refactor our application so that it's composed of three smaller
components, one component for displaying the counter and two components for buttons.
Let's first implement a Display component that's responsible for displaying the value of the counter.
One best practice in React is to lift the state up in the component hierarchy. The documentation
says:
Often, several components need to reflect the same changing data. We recommend lifting
the shared state up to their closest common ancestor.
So let's place the application's state in the App component and pass it down to the Display
component through props:
https://fullstackopen.com/en/part1/component_state_event_handlers 12/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
Using the component is straightforward, as we only need to pass the state of the counter to it:
return (
<div>
<Display counter={counter}/>
<button onClick={increaseByOne}>
plus
</button>
<button onClick={setToZero}>
zero
</button>
</div>
)
}
Everything still works. When the buttons are clicked and the App gets re-rendered, all of its children
including the Display component are also re-rendered.
Next, let's make a Button component for the buttons of our application. We have to pass the event
handler as well as the title of the button through the component's props:
https://fullstackopen.com/en/part1/component_state_event_handlers 13/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
const setToZero = () => setCounter(0)
return (
<div>
<Display counter={counter}/>
<Button
onClick={increaseByOne}
text='plus'
/>
<Button
onClick={setToZero}
text='zero'
/>
<Button
onClick={decreaseByOne}
text='minus'
/>
</div>
)
}
Since we now have an easily reusable Button component, we've also implemented new functionality
into our application by adding a button that can be used to decrement the counter.
The event handler is passed to the Button component through the onClick prop. The name of the
prop itself is not that significant, but our naming choice wasn't completely random.
React's own official tutorial suggests: "In React, it’s conventional to use onSomething names for
props which take functions which handle events and handleSomething for the actual function
definitions which handle those events."
Let's go over the main principles of how an application works once more.
When the application starts, the code in App is executed. This code uses a useState hook to create
the application state, setting an initial value of the variable counter . This component contains the
Display component - which displays the counter's value, 0 - and three Button components.
The buttons all have event handlers, which are used to change the state of the counter.
When one of the buttons is clicked, the event handler is executed. The event handler changes the
state of the App component with the setCounter function. Calling a function that changes the
state causes the component to re-render.
So, if a user clicks the plus button, the button's event handler changes the value of counter to 1,
and the App component is re-rendered. This causes its subcomponents Display and Button
to also be re-rendered. Display receives the new value of the counter, 1, as props. The Button
components receive event handlers which can be used to change the state of the counter.
To be sure to understand how the program works, let us add some console.log statements to it
https://fullstackopen.com/en/part1/component_state_event_handlers 14/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
console.log('rendering with counter value', counter)
return (
<div>
<Display counter={counter} />
<Button onClick={increaseByOne} text="plus" />
<Button onClick={setToZero} text="zero" />
<Button onClick={decreaseByOne} text="minus" />
</div>
)
}
Let us now see what gets rendered to the console when the buttons plus, zero and minus are
pressed:
Do not ever try to guess what your code does. It is just better to use console.log and see with
your own eyes what it does.
https://fullstackopen.com/en/part1/component_state_event_handlers 15/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
The component only uses the counter field of its props. This means we can simplify the
component by using destructuring , like so:
The function defining the component contains only the return statement, so we can define the
function using the more compact form of arrow functions:
We can use destructuring to get only the required fields from props, and use the more compact form
of arrow functions:
NB: While building your own components, you can name their event handler props anyway you like,
for this you can refer to the react's documentation on Naming event handler props . It goes as
following:
By convention, event handler props should start with on , followed by a capital letter. For
example, the Button component’s onClick prop could have been called onSmash :
https://fullstackopen.com/en/part1/component_state_event_handlers 16/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
We can simplify the Button component once more by declaring the return statement in just one line:
NB: However, be careful to not oversimplify your components, as this makes adding complexity a
more tedious task down the road.
Part 1b Part 1d
Previous part Next part
About course
Course contents
FAQ
Partners
Challenge
https://fullstackopen.com/en/part1/component_state_event_handlers 17/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
https://fullstackopen.com/en/part1/component_state_event_handlers 18/19
2/20/24, 2:31 PM Fullstack part1 | Component state, event handlers
https://fullstackopen.com/en/part1/component_state_event_handlers 19/19