Appearance
Escape Hatches
FreshSome components need to interact with systems outside of React: focus an input, play a video, connect to a chat server. These "escape hatches" let you step outside React when needed.
Referencing Values with Refs
When you want a component to "remember" some information but don't want that information to trigger new renders, use a ref:
jsx
import { useRef } from 'react';
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return <button onClick={handleClick}>Click me!</button>;
}Refs vs State
| Refs | State |
|---|---|
useRef(initialValue) returns { current: initialValue } | useState(initialValue) returns [value, setValue] |
| Doesn't trigger re-render when changed | Triggers re-render when changed |
Mutable: modify ref.current directly | Immutable: use setter function |
Don't read/write current during rendering | Can read state at any time |
Manipulating the DOM with Refs
React automatically updates the DOM, but sometimes you need direct access. Use a ref to get a DOM node:
jsx
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>Focus the input</button>
</>
);
}Scrolling to an Element
jsx
function ScrollToItem() {
const listRef = useRef(null);
function scrollToIndex(index) {
const listNode = listRef.current;
const imgNode = listNode.querySelectorAll('li > img')[index];
imgNode.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
return (
<ul ref={listRef}>
{items.map((item, i) => (
<li key={item.id}>
<img src={item.src} alt={item.alt} />
</li>
))}
</ul>
);
}Ref Callbacks
For dynamic lists where you need refs to multiple elements:
jsx
function getMap() {
if (!itemsRef.current) {
itemsRef.current = new Map();
}
return itemsRef.current;
}
<li
key={cat.id}
ref={(node) => {
const map = getMap();
map.set(cat.id, node);
return () => { map.delete(cat.id); };
}}
>Forwarding Refs
By default, React does not let a component access the DOM nodes of another component. A component that wants to expose its DOM node must opt in with forwardRef:
jsx
import { forwardRef, useRef } from 'react';
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
export default function Form() {
const inputRef = useRef(null);
return (
<>
<MyInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</>
);
}Synchronizing with Effects
Effects let you run code after rendering. Use them to synchronize with external systems (network, browser APIs, third-party libraries):
jsx
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}, [isPlaying]);
return <video ref={ref} src={src} loop playsInline />;
}Effect Lifecycle
flowchart TD
A[Component mounts] --> B[Effect runs]
B --> C{Dependencies change?}
C -->|Yes| D[Cleanup runs]
D --> B
C -->|No| E[Wait...]
E --> C
F[Component unmounts] --> G[Final cleanup runs]Cleanup Function
Return a cleanup function from your effect:
jsx
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect(); // cleanup
}, [serverUrl, roomId]);You Might Not Need an Effect
Common cases where effects are unnecessary:
- Transforming data for rendering: compute during render instead
- Handling user events: use event handlers, not effects
- Resetting state on prop change: use a
keyinstead - Adjusting state on prop change: compute during render
jsx
// BAD: effect to filter a list
const [items, setItems] = useState(allItems);
useEffect(() => {
setItems(allItems.filter(item => item.category === category));
}, [allItems, category]);
// GOOD: compute during render
const items = allItems.filter(item => item.category === category);Custom Hooks
Extract reusable logic into custom Hooks:
jsx
import { useState, useEffect } from 'react';
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function handleOnline() { setIsOnline(true); }
function handleOffline() { setIsOnline(false); }
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
function StatusBar() {
const isOnline = useOnlineStatus();
return <h1>{isOnline ? 'Online' : 'Disconnected'}</h1>;
}Custom Hook Rules
- Names must start with
usefollowed by a capital letter - Custom Hooks share stateful logic, not state itself
- Each call to a Hook gets its own isolated state
When to Extract a Custom Hook
If you find yourself writing the same effect logic in multiple components, extract it into a custom Hook. If a piece of logic could be a standalone function (no hooks inside), it should be a regular function, not a Hook.