PresentationInteractive, narrated presentation of the section content.
ContentDetailed description of the section content.
Event handlers in React are functions that are triggered when user interactions occur, such as clicking a button, typing in an input field, or submitting a form.
Key Takeways:
- React events are similar to HTML DOM events but are wrapped in React's synthetic event system for consistency across browsers.
- Event handlers are added to elements as attributes using camelCase (e.g., onClick, onChange).
- The handler function often interacts with the component state using the state hook.
- You can create custom event handler functions or use arrow notation to create event handlers in place
- If you create custom event handlers, you must type them manually
Click Handling
In this course, we already used a click handler in several places. For example, consider this react code, where clicking on the button changes the component state and shows a user message.
import React, { useState } from "react";
function FeedbackForm() {
const [message, setMessage] = useState("");
const handleClick = (e: MouseEventHandler<HTMLButtonElement>) => {
setMessage("Thank you for your feedback!");
};
return (
<div>
<button onClick={handleClick}>Submit Feedback</button>
<p>{message}</p>
</div>
);
}In the example above, note the manual type of the event e in the event handler. You can easily find out the type of the event handler, by hovering your mouse over the events in your editor.
The other option to create your ecent handlers is in place of the prop using the array notation. We can rewrite the avove example as following:
function FeedbackForm() {
const [message, setMessage] = useState("");
return (
<div>
<button
onClick={(e: MouseEventHandler<HTMLButtonElement>) => {
setMessage("Thank you for your feedback!");
}}
>
Submit Feedback
</button>
<p>{message}</p>
</div>
);
}We leave it up to you to choose which method you prefer, but the first one delivers cleaner code with a clear sepearation of functionality, leaving JSX to view your code and yor functions to take care of reactivity.
Forms
HTML introduces several form elements which you can use to collect data from your users. The form components can either be controlled or uncontrolled. Let's take a look.
Uncontrolled Components
In an uncontrolled component, the form element maintains its own internal state, and React does not directly control its value. Instead, you use a ref and the newly itroduced useRef hook to access the current value when needed. With ref, React knows which HTML element it deals with. The value of the referenced element is accessible through current property. Check out this example:
import React, { useRef } from "react";
function UncontrolledForm() {
const inputRef = useRef();
const handleSubmit = () => {
alert(`Input Value: ${inputRef.current.value}`);
};
return (
<div>
<label htmlFor="title">Title</label>
<input id="title" type="text" ref={inputRef} />
<button onClick={handleSubmit}>Submit</button>
</div>
);
}While the advantage of this approach is a simple code, it also is not the React way, bringing additional complexity down the road, for example if we would want to reactively display the value from the input.
Controlled Components
Let's add a new onChange handler that will track the value of the title and control the value displayed in the component.
import React, { useRef } from "react";
function UncontrolledForm() {
const [title, setTitle] = useState();
const changeTitle = (e: ChangeEventHandler<HTMLInputElement>) => {
setTitle(e.currentTarget.value)
};
return (
<div>
<h1>{title ?? "No title"}</h1>
<label htmlFor="title">Title</label>
<input id="title" value={title} type="text" onChange={changeTitle} />
</div>
);
}Try removing the onChange handler from the input component and type a new value. What do you observe? Why changing value does not work?
With more complicated forms, you do not want to create a separate state per each component. Instead, you can use form element attributes to create a more generate event handler:
import React, { useRef } from "react";
function UncontrolledForm() {
const [formState, setFormState] = useState({
title: "",
content: ""
});
const updateFormState = (e: ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>) => {
setFormState({
...formState,
[e.currentTarget.name]: e.currentTarget.value
})
};
return (
<div>
<h1>{title ?? "No title"}</h1>
<label htmlFor="title">Title</label>
<input name="title" value={title} type="text" onChange={updateFormState} />
<label htmlFor="content">Content</label>
<textarea name="content" value={content} type="text" onChange={updateFormState} />
</div>
);
}Let's make this example even more resilient to human error. For example, we want the title to be a compulsory value. When user loses focus (blurs) from the title input and does not type anything, we will paint it red and show an error message.
import React, { useRef } from "react";
// new error display component
function Error({ children }) {
return (
<div style={{ color: 'red', fontWeight: 'bold'}}>
⚠️ {error}
</div>
)
}
function UncontrolledForm() {
const { formState, updateFormState } = useformState({
title: "",
content: ""
});
// new state holding errors
const [errors, setErrors] = useState({
title: "",
content: ""
});
// validation function
const validate = () => {
const errors = {
title: "",
content: ""
}
if (title == '') {
errors.title = "Title is required";
}
if (content.length < 20) {
errors.content = "Too short"
}
setErrors(errors);
}
const updateFormState = (e: ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>) => {
setFormState({
...formState,
[e.currentTarget.name]: e.currentTarget.value
});
validate();
};
return (
<div>
<h1>{title ?? "No title"}</h1>
<label htmlFor="title">Title</label>
<input
name="title"
value={title}
type="text"
onChange={updateFormState}
onBlur={validate}
/>
{errors.title && <Error>{errors.title}</Error>}
<label htmlFor="content">Content</label>
<textarea
name="content"
value={content}
type="text"
onChange={updateFormState}
onBlur={validate}
/>
{errors.content && <Error>{errors.content}</Error>}
</div>
);
}Let's simplify things a bit and introduce a custom hook to help us with a form state:
import React, { useRef } from "react";
function useformState<T>(initialState<T>) {
const [formState, setFormState] = useState(initialState);
const [errors, setErrors] = useState<T>({});
return {
formState,
setFormState(partialState: Partial<T>) {
setFormState({
...formState,
...partialState
})
}
updateFormState(e: ChangeEventHandler<HTMLInputElement>) {
setFormState({
...formState,
[e.currentTarget.name]: e.currentTarget.value
})
}
errors,
setErrors
}
}
function Form() {
const { formState, updateFormState, errors, setErrors } = useFormState({
title: "",
content: ""
});
// validation function
const validate = () => {
const errors = {
title: "",
content: ""
}
if (title == '') {
errors.title = "Title is required";
}
if (content.length < 20) {
errors.content = "Too short"
}
setErrors(errors);
}
return (
<div>
<h1>{title ?? "No title"}</h1>
<label htmlFor="title">Title</label>
<input
name="title"
value={title}
type="text"
onChange={updateFormState}
onBlur={validate}
/>
{errors.title && <Error>{errors.title}</Error>}
<label htmlFor="content">Content</label>
<textarea
name="content"
value={content}
type="text"
onChange={updateFormState}
onBlur={validate}
/>
{errors.content && <Error>{errors.content}</Error>}
</div>
);
}But what about Selects and Checkboxes? Let's take a look at some examples:
Checkboxes
With checkboxes, we use “checked” property of input to track the state:
import React, { useRef } from "react";
function Form() {
const { formState, setFormState } = useFormState({ agree: false });
return (
<div>
<input
id="agree"
name="agree"
checked={formState.agree}
type="checkbox"
onChange={setFormState} />
<label htmlFor="agree">Title</label>
</div>
);
}For this we will have to update our custom hook:
updateFormState(e: ChangeEventHandler<HTMLInputElement>) {
setFormState({
...formState,
[e.currentTarget.name]: e.type === 'checkbox'
? e.currentValue.checked
: e.currentTarget.value
})
}Selects
Similarly we can handle selects:
import React, { useRef } from "react";
function Form() {
const { formState, setFormState } = useFormState({ agree: false });
return (
<div>
<label htmlFor="agree">Title</label>
<select
id="car"
name="car"
value={formState.agree}
type="checkbox"
onChange={setFormState}
>
<option value="">Please Select</option>
["Mazda", "Toyota", "BMW"].map(t => <option key={t} value={t}>{t}</option>)
</select>
</div>
);
}There is a lot of room for improvement, as we can handle better the form state, or include form validation in our custom hook. But, before you go creating overly complex form handling mechanism, check out one of the existing form libraries for react https://dev.to/ansonch/5-best-libraries-to-develop-reactjs-forms-2024-49k3.
No hints available.