Summary: Learning about React Event Handling and Hooks.
Event Handling
In React, event handling is a key concept that allows us to capture and respond to user actions like clicks, form submissions, key presses, etc. React’s event system is very similar to handling events in vanilla JavaScript, but it has its own syntactical differences and best practices.
Events in JSX
- In React, we pass a function as an event handler rather than a string (like in plain HTML).
- Event names in React are written in camelCase instead of lowercase. For example:
onClick
instead ofonclick
onChange
instead ofonchange
- We don’t need to call
addEventListener
. We simply define the event handler inline.
Passing Functions as Event Handlers
- We can pass a function directly as an event handler, or we can pass an inline function.
- The function can reference the event object or any other data passed to it.
Synthetic Events
- React uses a cross-browser wrapper around the browser’s native event system called SyntheticEvent.
- SyntheticEvent normalizes events to ensure they behave consistently across different browsers.
- These synthetic events are faster and more efficient because React can pool and reuse them for performance reasons.
Binding Event Handlers
- In React components, especially class components, we need to bind event handlers to the component’s context (
this
) using:- Arrow functions: Automatically bind the function to the component instance.
- bind() method: Use
this.handleClick.bind(this)
to manually bind the function.
Prevent Default and Stop Propagation
- Just like in regular HTML, we can prevent default actions or stop event propagation by calling
event.preventDefault()
andevent.stopPropagation()
, respectively.
Functional vs Class Components
- In modern React (using Hooks), functional components are the most commonly used, and events are handled within them differently compared to class components.
import React, { useState } from 'react';
function EventHandlingExample() {
// State to store the input value and button click count
const [inputValue, setInputValue] = useState('');
const [clickCount, setClickCount] = useState(0);
// Handle input change event
const handleInputChange = (event) => {
// 'event' is the SyntheticEvent passed to this function
setInputValue(event.target.value);
};
// Handle button click event
const handleClick = () => {
setClickCount(clickCount + 1);
};
// Prevent default form submission
const handleFormSubmit = (event) => {
event.preventDefault(); // Prevents page reload on form submission
alert(`Form submitted with input: ${inputValue}`);
};
return (
<div>
{/* Input field with onChange event */}
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="Type something here"
/>
<p>Input Value: {inputValue}</p>
{/* Button with onClick event */}
<button onClick={handleClick}>
Click me!
</button>
<p>Button Clicked {clickCount} times</p>
{/* Form with onSubmit event */}
<form onSubmit={handleFormSubmit}>
<button type="submit">Submit Form</button>
</form>
</div>
);
}
export default EventHandlingExample;
- Input Change Event (
onChange
) - The
handleInputChange
function is triggered every time the value of the input changes. The event object contains useful information likeevent.target.value
, which is the current value of the input field. - This value is then set into the
inputValue
state usingsetInputValue()
, and this updated value is displayed below the input field. - Button Click Event (
onClick
): - The
handleClick
function is triggered when the button is clicked. This increases theclickCount
by 1, and the updated count is displayed on the screen. - Form Submit Event (
onSubmit
): - The form submission’s default behavior (which would reload the page) is prevented by
event.preventDefault()
. - It then alerts the current value of the
inputValue
state to demonstrate form handling.
Event Binding in Class Components
If we are using class components event handling is slightly different. We must explicitly bind this
to event handlers.
import React, { Component } from 'react';
class EventHandlingClassExample extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
clickCount: 0,
};
// Bind the event handler to `this`
this.handleInputChange = this.handleInputChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleInputChange(event) {
this.setState({ inputValue: event.target.value });
}
handleClick() {
this.setState((prevState) => ({
clickCount: prevState.clickCount + 1
}));
}
render() {
return (
<div>
<input
type="text"
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<p>Input Value: {this.state.inputValue}</p>
<button onClick={this.handleClick}>
Click me!
</button>
<p>Button Clicked {this.state.clickCount} times</p>
</div>
);
}
}
export default EventHandlingClassExample;
Hooks
React Hooks are a powerful feature introduced in React 16.8 that allow us to use state and other React features in functional components, without needing to write class components. Hooks simplify component logic, making it easier to manage state, lifecycle, and side effects in functional components.
Why Hooks?
Before hooks, class components were required to manage component state and lifecycle methods, which could be cumbersome.
- Reuse logic: Hooks allow us to extract and reuse stateful logic across multiple components.
- Simplify components: Functional components with hooks tend to be simpler and cleaner compared to class components.
- Reduce complexity: Hooks eliminate the need for complex class hierarchies and make code easier to maintain and test.
useState
: Manages local state in functional components.useEffect
: Handles side effects like data fetching, subscriptions, and DOM manipulations.useContext
: Provides access to React’s context API, making it easier to share state between components.useReducer
: Manages more complex state logic that involves multiple sub-values or requires a centralized state management.useRef
: Gives access to DOM elements or mutable variables that persist across renders.useCallback
: Memoizes a function to prevent unnecessary re-creations on every render.useMemo
: Memoizes the result of a computation, optimizing performance.useLayoutEffect
: LikeuseEffect
, but runs synchronously after all DOM mutations, useful for measuring DOM nodes.useImperativeHandle
: Customizes the instance value exposed to parent components when usingref
.
useState
The useState
hook allows functional components to hold and manage local state. It returns two values: the current state and a function to update that state.
import React, { useState } from 'react';
function Counter() {
// Declare a state variable 'count' with an initial value of 0
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useState
takes an initial state value (0
in this case) and returns a pair:- State variable:
count
(the current state value). - State updater function:
setCount
(a function to update the state).
useEffect
The useEffect
hook allows us to perform side effects in functional components.
- Fetching data from an API.
- Directly interacting with the DOM (like setting a title).
- Setting up a subscription.
import React, { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
// Fetch data when the component mounts
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => setData(data));
// Optionally, return a cleanup function to run when the component unmounts
return () => {
console.log('Component unmounted, cleanup any side effects.');
};
}, []); // Empty dependency array means this effect runs once, after the first render
return (
<div>
<p>Fetched Data: {JSON.stringify(data)}</p>
</div>
);
}
useEffect
takes two arguments:- The effect function: The function that runs after the component renders (this is where we perform side effects like fetching data).
- The dependency array (optional): Controls when the effect should re-run. If empty, the effect only runs after the first render (similar to
componentDidMount
in class components).
useContext
useContext
provides an easy way to consume values from React’s Context API. Context allows for sharing state or functions globally across a component tree, without needing to pass props down manually at every level.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function ThemeButton() {
// Access the current value of ThemeContext
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
{theme === 'dark' ? 'Dark Theme' : 'Light Theme'}
</button>
);
}
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemeButton />
</ThemeContext.Provider>
);
}
useContext
takes a context object (created withcreateContext
) and returns the current context value.- The component doesn’t need to pass props down manually; instead, it can directly access the context value.
useReducer
useReducer
is similar to useState
but is more suited for managing complex state logic, such as when state depends on previous values or when we have multiple state transitions.
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
useReducer
takes two arguments:- A reducer function that takes the current state and an action, and returns a new state.
- The initial state value.
- It returns the state and a dispatch function that we can use to trigger actions and update the state.
useRef
- Access and manipulate DOM elements.
- Hold mutable values that persist across renders without causing a re-render.
import React, { useRef } from 'react';
function FocusInput() {
const inputRef = useRef(null);
const handleFocus = () => {
// Focus the input field when the button is clicked
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleFocus}>Focus the input</button>
</div>
);
}
useRef
creates a reference that can be attached to a DOM element via theref
attribute.inputRef.current
gives us access to the DOM node, allowing us to manipulate it (e.g., focusing the input).
useCallback
useCallback
is used to memoize functions to prevent them from being re-created on every render unless their dependencies change. This is useful for optimizing performance in child components that rely on stable references for props.
import React, { useState, useCallback } from 'react';
function Child({ handleClick }) {
console.log('Child re-rendered');
return <button onClick={handleClick}>Click me!</button>;
}
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []); // Function won't be recreated unless the dependencies change
return (
<div>
<Child handleClick={handleClick} />
<button onClick={() => setCount(count + 1)}>Re-render Parent</button>
</div>
);
}
useCallback
ensures that thehandleClick
function remains stable across renders unless the dependency array changes.- Without
useCallback
,handleClick
would be re-created on every render, causing unnecessary re-renders of theChild
component.
useMemo
useMemo
is used to memoize expensive computations, so they are only recomputed when necessary.
import React, { useState, useMemo } from 'react';
function ExpensiveCalculation({ num }) {
const computeExpensiveValue = (n) => {
console.log('Calculating...');
return n * 2;
};
const memoizedValue = useMemo(() => computeExpensiveValue(num), [num]);
return <div>Computed Value: {memoizedValue}</div>;
}
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<ExpensiveCalculation num={count} />
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Leave a Reply