There are many similarities when using setting up React events, such as onClick and onMouseOver.
We will first look at the button onClick event, and how to bind an event handler to it.
There are many similarities when using setting up React events, such as onClick and onMouseOver.
We will first look at the button onClick event, and how to bind an event handler to it.
Afterwards, we will learn how to set up an event handler from another Razor functional component.
From there, we will look at onMouseOver and onMouseOut events. This will help us to change the background colour when we hover our mouse over a certain element.
Finally, we will look at how to set up async event handlers.
We are going to use a notes app to demonstrate these React events.
This application has been created as a React TypeScript app. There are many benefits with using TypeScript, including being able to define strongly typed variables, which leads to better error handling.
Eagle-eyed readers will realise that this is the same application as we used when looking at the button onclick event in Blazor WebAssembly.
Learn Blazor WebAssembly with our online courses
We begin with a Note
class. The Note
class stores the note's message and when the created date.
/* Note.tsx */
class Note {
Message: string;
Created: Date;
constructor(message: string) {
this.Message = message;
this.Created = new Date();
}
}
export default Note;
From there, we set up two Razor functional components.
The first is NoteViewComponent
, and this will display the note's message, and the created date. We have set up a NoteViewComponentProps
interface and this has a Note
instance. This interface will be used as the NoteViewComponent
props instance.
/* NoteViewComponent.tsx */
import React from "react";
import Note from "../note/note";
import './note-view-component.css'
interface NoteViewComponentProps {
Note: Note;
}
const NoteViewComponent: React.FC<NoteViewComponentProps> = (props) => {
return <li>
<span>{props.Note.Message}</span>
<span>Created: {props.Note.Created.toString()}</span>
</li>
}
export default NoteViewComponent;
The second is NoteListingComponent
. This will display all the created notes, and allows the user to create a new note.
We have set up a NoteListingComponentProps
interface. This interface will store all the Notes as an array. It will then use that array to loop through all the notes, and pass the Note instance to a NoteViewComponent
instance.
/* NoteListingComponent.tsx */
import React, { useState } from "react";
import Note from "../note/note";
import NoteViewComponent from "./note-view-component";
import './note-listing-component.css'
interface NoteListingComponentProps {
Notes: Note[];
}
const NoteListingComponent: React.FC<NoteListingComponentProps> = (props) => {
const [notes, setNotes] = useState(props.Notes);
return <>
<div className="col-6">
<h2>Enter your note</h2>
<fieldset>
<label htmlFor="Comment">
<textarea id="Comment" cols={50} rows={6}></textarea>
</label>
</fieldset>
<button type="submit">Submit</button>
</div>
<div className="col-6">
<h2>Your saved notes</h2>
{notes && notes.length > 0 ? (
<ul>
{notes.map((note: Note, index: number) => {
return <NoteViewComponent key={`Note`+index} Note={note}></NoteViewComponent>
})}
</ul>
) : (
<p>You currently do not have any saved notes.</p>
)}
</div>
</>
}
export default NoteListingComponent;
Our first task is to add a button onClick event, and we will use this so we are able to add a note to our app.
What we will do is create a newNote
string type. Using React's useState
hook, we create a pair of values. The first is newNote
which is the variable that stores the message. The other is setNewNote
which is a function that you can call to update the newNote
variable.
When calling the function stored in useState
, it updates the functional component, assuming that the new value is different from the old value.
/* NoteListingComponent.tsx */
...
const NoteListingComponent: React.FC<NoteListingComponentProps> = (props) => {
...
const [newNote, setNewNote] = useState('');
...
}
...
Afterwards, we need to set up a function. This function will help store the message when typing a new message. The newNote
variable will be updated every time there is a keyboard input.
/* NoteListingComponent.tsx */
import React, { ChangeEvent, useState } from "react";
...
const NoteListingComponent: React.FC<NoteListingComponentProps> = (props) => {
...
const onNoteChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
if (event && event.target) {
setNewNote(event.target.value);
}
}
...
}
...
Finally, we need to bind our onNoteChange
function to the React onChange
event as an event handler in our text box. This will mean that when the onChange
event is fired, it will execute the onNoteChange
function to update the newNote
instance.
/* NoteListingComponent.tsx */
...
const NoteListingComponent: React.FC<NoteListingComponentProps> = (props) => {
...
return <>
...
<fieldset>
<label htmlFor="Comment">
<textarea id="Comment" cols={50} rows={6} onChange={onNoteChange} value={newNote}></textarea>
</label>
</fieldset>
...
</>
}
...
Now that we have the newNote
variable that stores what we are typing in our notes text box, the next thing we need to do is to add the note's message to our notes list.
To do that, we set up an onCreateNote
function. What this will do is add a new Note
to our notes list, and then reset the value in our newNote
variable. This will empty the text box when a new note is created.
/* NoteListingComponent.tsx */
import React, { ChangeEvent, MouseEvent, useState } from "react";
...
const NoteListingComponent: React.FC<NoteListingComponentProps> = (props) => {
const [notes, setNotes] = useState(props.Notes);
const [newNote, setNewNote] = useState('');
const onCreateNote = (event: MouseEvent<HTMLButtonElement>) => {
if (event) {
event.preventDefault();
setNotes([...notes, new Note(newNote)]);
setNewNote("");
}
}
...
}
...
Finally, we need to bind our onCreateNote
function to the button onClick
event as an event handler. This will create our note and add it to our notes list.
/* NoteListingComponent.tsx */
...
const NoteListingComponent: React.FC<NoteListingComponentProps> = (props) => {
...
return <>
...
<button type="submit" onClick={onCreateNote}>Submit</button>
...
</>
}
...
That's it! We can now write a new note message and add it to our notes listing.
Our next task is to call a button onClick event handler in another component.
We will demonstrate this by deleting a note from our notes listing.
The issue with this is that our delete button is located in NoteViewComponent
.
However, we need to delete the note from our notes list. The notes list is located in NoteListingComponent
, so that means we need to set the function in NoteListingComponent
.
So, how do we go about setting an event handler to an event in another component?
Well, what we can do is set a new event handler in the NoteViewComponentProps
interface. When we create a NoteViewComponent
instance, it will need to pass in the event handler as a parameter.
/* NoteViewComponent.tsx */
import React, {MouseEventHandler} from "react";
...
interface NoteViewComponentProps {
Note: Note;
OnDeleteNote: MouseEventHandler<HTMLButtonElement>
}
...
Now that we have created an event handler for deleting a note, we need to create a delete button and bind our event handler to the delete button's onClick event.
/* NoteViewComponent.tsx */
...
const NoteViewComponent: React.FC<NoteViewComponentProps> = (props) => {
return <li>
<span>{props.Note.Message}</span>
<span>Created: {props.Note.Created.toString()}</span>
<button type="submit" onClick={props.OnDeleteNote}>Delete</button>
</li>
}
...
Next, we can move to our NoteListingComponent
, and set up the delete note function. What this will do is pass in the array index number of the note we wish to delete. The app will then filter out the note with that index number from the notes list.
/* NoteListingComponent.tsx */
...
const NoteListingComponent: React.FC<NoteListingComponentProps> = (props) => {
...
const onDeleteNote = (event: MouseEvent<HTMLButtonElement>, index: number) => {
if (event) {
event.preventDefault();
setNotes(notes.filter((note, noteIndex) => noteIndex != index));
}
}
...
}
...
Finally, as we set up the onDeleteNote
function in NoteViewComponent
, we now need to bind the event handler to it. As well as the event, we also pass in the array index as a parameter. This is so the app knows which note to delete.
/* NoteListingComponent.tsx */
...
const NoteListingComponent: React.FC<NoteListingComponentProps> = (props) => {
...
{notes && notes.length > 0 ? (
<ul>
{notes.map((note: Note, index: number) => {
return <NoteViewComponent key={`Note`+index} Note={note} OnDeleteNote={(event) => onDeleteNote(event, index)}></NoteViewComponent>
})}
</ul>
) : (
<p>You currently do not have any saved notes.</p>
)}
...
}
...
We can now successfully delete notes from our note app.
We will look at some of the other React events, and for this, we will use the onMouseOver and onMouseOut React events to change the background colour of a note when the mouse hovers over it.
In note-view-component.css
, we have added a .highlight
class which sets the background colour to grey.
/* note-view-component.css */
...
.highlight {
background-color: #ccc;
}
Next, we are going to use the React useState
hook to declare a class name that we will be able to change.
/* NoteViewComponent.tsx */
...
const NoteViewComponent: React.FC<NoteViewComponentProps> = (props) => {
const [className, setClassName] = useState('');
...
}
...
Afterwards, we are going to set up two functions. The onChangeBackground
function will set the class name to highlight, and the onRemoveBackground
function will empty the class name.
/* NoteViewComponent.tsx */
import React, {useState, MouseEvent, MouseEventHandler} from "react";
...
const NoteViewComponent: React.FC<NoteViewComponentProps> = (props) => {
...
const onChangeBackground = (event: MouseEvent<HTMLLIElement>) => {
if (event) {
setClassName("highlight");
}
};
const onRemoveBackground = (event: MouseEvent<HTMLLIElement>) => {
if (event) {
setClassName("");
}
};
...
}
...
We now need to set up these functions as event handlers. As we are going to change the background colour for our note, we are going to set these as events in our <li> tag.
The onMouseOver
will be the first event, and this is used when a mouse is hovering over the element. We will bind our onChangeBackground
function to the onMouseOver
event. This means that when we hover our mouse over a note, it will change the background colour to grey.
To remove the background, we will use the onMouseOut
event, and this will bind to our onRemoveBackground
function. This will remove the .highlight
class from the <li>
tag, which will restore the background colour to white.
/* NoteViewComponent.tsx */
...
const NoteViewComponent: React.FC<NoteViewComponentProps> = (props) => {
...
return <li className={className} onMouseOver={onChangeBackground} onMouseOut={onRemoveBackground}>
<span>{props.Note.Message}</span>
<span>Created: {props.Note.Created.toString()}</span>
<button type="submit" onClick={props.OnDeleteNote}>Delete</button>
</li>
}
...
This now allows us to highlight a note by moving our mouse cursor over it.
We will show you how to set up an asynchronous event handler. Using an async event handler is important, particularly for making API calls where the response times could be dependant on many factors, such as internet connection, or database performance.
Using an async event handler means that the app isn't locked down whilst a particular event handler is being executed.
To demonstrate how this works, we are going to implement a two-second delay when creating a note. The note will not appear on our notes listing for at least two seconds.
For this example, we are set the function into a while loop for two seconds. When this happens, we are unable to do anything else with the app. The background colour does not change when hovering over another note when we are caught in this loop.
/* NoteListingComponent.tsx */
...
const NoteListingComponent: React.FC<NoteListingComponentProps> = (props) => {
...
const onCreateNote = (event: MouseEvent<HTMLButtonElement>) => {
if (event) {
event.preventDefault();
var comingDate = new Date();
comingDate.setSeconds(comingDate.getSeconds() + 2);
while (comingDate > new Date()) {
}
setNotes([...notes, new Note(newNote)]);
setNewNote("");
}
}
...
}
...
To resolve this issue, we can set up a async function for this event. We can use the JavaScript setTimeout
function and delay it for two seconds (or 2000 milliseconds).
In the async function, we can wrap the setTimeout
into a Promise
and await the Promise
result.
/* NoteListingComponent.tsx */
...
const NoteListingComponent: React.FC<NoteListingComponentProps> = (props) => {
...
const onCreateNoteAsync = async(event: MouseEvent<HTMLButtonElement>) => {
if (event) {
event.preventDefault();
await new Promise(resolve => setTimeout(resolve, 2000));
setNotes([...notes, new Note(newNote)]);
setNewNote("");
}
}
...
}
...
The final task is to bind our async function to the button onClick event as an async event handler. We need to declare the button onClick event as async, and use the await keyword.
/* NoteListingComponent.tsx */
...
const NoteListingComponent: React.FC<NoteListingComponentProps> = (props) => {
...
return <>
...
<button type="submit" onClick={async(e) => await onCreateNoteAsync(e)}>Submit</button>
...
</>
}
...
When running our application now, we can always hover over a note, even if we in the process of the two second delay.
Watch us demonstrate how to implement these React events, and how to set up an async event handler in our video below:
In addition, download our code example to try it out and give it a go.