The primary purpose of a React component is to return some JSX to be rendered. Often, it is helpful for a component to execute some code that performs side effects in addition to rendering JSX.
In function components, we manage side effects with the Effect Hook. Some common side effects include: fetching data from a server, subscribing to a data stream, logging values to the console, interval timers, and directly interacting with the DOM.
The useEffect hook runs side effects after a render, with the frequency controlled by its dependency array. It takes two arguments in the form useEffect(callback, dependencies). The callback contains the side‑effect logic and runs after the initial render and again whenever one of the dependencies changes (or after every render if you omit the dependency array).
import React, { useState, useEffect } from 'react';function TitleCount() {const [count, setCount] = useState(0);useEffect(() => {document.title = `You clicked ${count} times`;}, [count]);return <button onClick={() => setCount(prev => prev + 1)}>+</button>;}
The dependency array is used to tell the useEffect() method when to call the effect.
useEffect(() => {alert('called after every render');});useEffect(() => {alert('called after first render');}, []);useEffect(() => {alert('called when value of `endpoint` or `id` changes');}, [endpoint, id]);
In React, form fields are considered either uncontrolled, meaning they maintain their own state, or controlled, meaning that some parent maintains their state and passes it to them to display. Usually, the form fields will be controlled.
The example code shows an uncontrolled and controlled input.
const uncontrolledInput = <input />;const controlledInput = (<input value={stateValue} onChange={handleInputChange} />);
Form validation in React can trigger at several points: while typing (onChange), upon exiting a field (onBlur), or before submitting (onSubmit). Each option balances real-time feedback against user experience.
Immediate validation, like onChange, helps prevent errors while typing, but might overwhelm users with feedback. onBlur is suitable for a polished UI, and onSubmit ensures thorough checks at the final stage.
import React, { useState } from 'react';function LoginForm() {const [email, setEmail] = useState('');const [error, setError] = useState('');const handleSubmit = (e) => {e.preventDefault();if (!email.includes('@')) {setError('Invalid email');return;}console.log('Submitted:', email);};return (<form onSubmit={handleSubmit}><inputtype="email"value={email}onChange={(e) => setEmail(e.target.value)}onBlur={() => email && !email.includes('@') && setError('Invalid email')}/>{error && <span>{error}</span>}<button type="submit">Submit</button></form>);}
The cleanup function is optionally returned by the first argument of the Effect Hook.
If the effect does anything that needs to be cleaned up to prevent memory leaks, then the effect returns a cleanup function. The cleanup function is called before re-running the effect (when dependencies change) and when the component unmounts.
useEffect(() => {document.addEventListener('keydown', handleKeydown);//Clean up the effect:return () => document.removeEventListener('keydown', handleKeydown);});
useEffectEvent HookThe useEffectEvent hook provides a way to efficiently manage dependencies by distinguishing between reactive and non-reactive values. It enables access to the latest values during side effects without causing unnecessary re-execution. By separating non-reactive logic, it avoids stale closures and reduces the need to list every changing value in an effect’s dependency array, which helps keep effects focused and easier to reason about.
This hook is handy when working with timers, subscriptions, and other external events that must always access up-to-date state without forcing unnecessary re-runs of the surrounding effect.
In this example, the chat connection logs incoming messages alongside the current draft text. Without useEffectEvent, including messagein the dependency array would cause the connection to reset with every keystroke. The hook allows the effect to access the latest draft value while keeping the dependency array focused onroomId`, the only value that should trigger reconnection.
import { useState, useEffect, useEffectEvent } from 'react';function ChatRoom({ roomId }) {const [message, setMessage] = useState('');// Effect Event: can read current 'message' without being reactive to itconst onMessage = useEffectEvent((msg) => {console.log(`Received: ${msg}, Current draft: ${message}`);});useEffect(() => {const connection = createConnection(roomId);connection.on('message', onMessage);connection.connect();return () => connection.disconnect();}, [roomId]); // Only reconnects when room changes, not on every keystrokereturn (<inputvalue={message}onChange={(e) => setMessage(e.target.value)}placeholder="Type a message..."/>);}
A controlled form element in React is built with a change handler function and a value attribute.
const controlledInput = (<input value={stateValue} onChange={handleInputChange} />);
React applications commonly juggle multiple interrelated state variables, demanding meticulous planning to ensure seamless updates are maintained across the app. Developers should consider scenarios like form handling, where a change to one field might necessitate updates elsewhere. This kind of interaction emphasizes the importance of a comprehensive understanding of state dependencies.
import React, { useState, useEffect } from 'react';function RegistrationForm() {const [username, setUsername] = useState('');const [email, setEmail] = useState('');const [isFormValid, setIsFormValid] = useState(false);// States interact: username and email changes affect form validityuseEffect(() => {const isValid = username.length >= 3 && email.includes('@');setIsFormValid(isValid);}, [username, email]);return (<div><inputtype="text"placeholder="Username (min 3 chars)"value={username}onChange={(e) => setUsername(e.target.value)}/><inputtype="email"placeholder="Email"value={email}onChange={(e) => setEmail(e.target.value)}/><button disabled={!isFormValid}>Submit</button>{isFormValid && <p>Form is ready to submit!</p>}</div>);}
React Derived StateValues based on the existing state or props should be calculated during render rather than stored separately. This strategy minimizes components’ complexity, enhances performance, and reduces the risk of inconsistencies. An example of this is dynamic UI adjustments, where a display might depend on computed values from current props or state.
function PriceDisplay({ basePrice, discount }) {const finalPrice = basePrice - discount;return <p>Final Price: {finalPrice}</p>;}