React Hooks
useState
Handle a local state in the component
https://react.dev/reference/react/useState
The dispatch or setter function always has access to the previous value
const [light, setLight] = useState('red');
const handleChangeColor = (color: string) => {
setLight((prev) => {
return color;
});
};
setTodos[...todos, newTodo];
useEffect
Run side effects and cleanup when unmounting component.
https://react.dev/reference/react/useEffect
It is recommended to create "atomic effects", I mean, have several useEffects where each one has a single, well-defined responsibility. This makes the code much easier to read, debug and maintain.
// countDown effect
useEffect(() => {
if (countdown === 0) return;
const intervalId = setInterval(() => {
setCountdown(prev => prev - 1);
}, 1000);
return () => {
clearInterval(intervalId);
}
}, [countdown]);
// change traffic light color effect
useEffect(() => {
if (countdown === 0) {
setCountdown(5);
if (light === 'red') {
setLight('green');
return;
}
if (light === 'yellow') {
setLight('red');
return;
}
if (light === 'green') {
setLight('yellow');
return;
}
}
return;
}, [countdown, light]);
You cannot declare the callback function of a useEffect directly as async because the hook optionally expects a cleanup function or undefined . async and await always returns a promise.
interface Pokemon {
id: number;
name: string;
imageUrl: string;
}
interface Props {
id: number;
}
const getPokemonById = async (id: number) => {
setIsLoading(true);
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
const data = await response.json();
setPokemon({
id: id,
name: data.name,
imageUrl: `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${id}.png`,
});
setIsLoading(false);
}
useEffect(() => {
getPokemonById(id);
}, [id]);
useContext
The React hook useContext allows access to the current value of a context object created with createContext.
To provide a value to the context, you must use a Context (Context.Provider in legacy apps) component.
import { useContext, createContext } from "react";
const ThemeContext = createContext('light');
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button className={theme}>
I am styled by theme context!
</button>
);
}
function App() {
return(
<ThemeContext value="dark">
<ThemedButton />
</ThemeContext>
);
}
useRef
Changing the value of a function's useState causes a rerender of the component, while changing the current property of a reference created with useRef does not. That is, useRef holds data that should not cause a UI refresh.
import { useRef } from "react"
export const FocusScreen = () => {
// Initial value null cause of react syncrony
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
console.log(inputRef.current?.value);
inputRef.current?.select();
}
return (
<div className="bg-gradient flex flex-col gap-4">
<h1 className="text-2xl font-light text-white">Focus screen</h1>
<input
ref={inputRef}
type="text"
className="bg-white text-black px-4 py-2 rounded-md"
autoFocus
/>
<button
className="bg-blue-500 text-white px-4 py-2 rounded-md cursor-pointer"
onClick={handleClick}>
Set Focus
</button>
</div>
)
}
useId
useCallback
Not neccesary since React v19. React Compiler automatically memoizes values and functions
The useCallback hook in React is used to memoize a callback function, ensuring that the same function reference is returned unless its dependencies change. This helps prevent unnecessary re-renders of child components that rely on the same function reference.
useMemo
Not neccesary since React v19. React Compiler automatically memoizes values and functions
useMemo is a React Hook that lets you cache the result of a calculation between re-renders.
useReducer
As your component grows and multiple related states come into play useReducer let´s you manage state updates in a centralized and predictable way.
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;
}
}
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
When to Use useReducer
- Managing complex state logic
- When next state depends on previous state
- Multiple related state variables
- When you want a Redux-like architecture in local components
useOptimistic
useOptimistic(state, updateFn)
useOptimistic is a new React Hook that lets you show a different state while an async action is underway.
const [comments, setComments] = useState<Comment[]>([
{ id: 1, text: 'Really awesomwe!' },
{ id: 2, text: 'I love it 🧡' },
]);
const handleAddComment = async (formdata: FormData) => {
const messageText = formdata.get('post-message');
addOptimisticComment(messageText);
// emulates backend response
await new Promise((resolve) => setTimeout(resolve, 3000));
setComments((prev) => [
...prev,
{
id: new Date().getTime(),
text: messageText,
},
]);
};
const [optimisticComments, addOptimisticComment] = useOptimistic(comments, (currentComments, newCommentText: string) => {
return [...currentComments, {
id: new Date().getTime(),
text: newCommentText,
optimistic: true,
}]
});
return (
<>
{/* map optimisticComments instead comments */}
{/* ... */}
{/* form */}
<form
action={event => handleAddComment(event)}
className="flex flex-col items-center justify-center bg-gray-300 w-[500px] rounded-b-3xl p-4"
>
<input
type="text"
name="post-message"
placeholder="Wite a comment"
required
className="w-full p-2 rounded-md mb-2 text-black bg-white"
/>
<button
type="submit"
disabled={false}
className="bg-blue-500 text-white p-2 rounded-md w-full">
Send
</button>
</form>
</>
)
The get() method of FormData interface returns the first value associated with a given key from within a FormData object. For example, in the previous code name="post-message" from an input type text
FormData: get method
useTransition
Allows rendering parts of the UI in the background. useTransition let you mark state updates as non-urgent transitions keeping the UI responsive during heavy renders.
isPendingis a boolean that indicates wheter a transition is currently running- Will be
trueuntilstartTransitionnon-urgent action resolves
In this scenario, typing updates the input immediately, while rendering the filtered list is marked as low priority.
import { useState, useTransition } from 'react';
function FilterableList({ items }) {
const [query, setQuery] = useState('');
const [filteredList, setFilteredList] = useState(items);
const [isPending, startTransition] = useTransition();
const handleSearch = (e) => {
const value = e.target.value;
// 1. Urgent update: Update input immediately
setQuery(value);
// 2. Non-urgent update: Defer heavy list recalculation/render
startTransition(() => {
setFilteredList(items.filter(item => item.includes(value)));
});
};
return (
<div>
<input type="text" value={query} onChange={handleSearch} />
{isPending ? (
<p>Updating list...</p>
) : (
<ul>
{filteredList.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
)}
</div>
);
}
Another use case combined with useOptimistic hook:
const [isPending, startTransition] = useTransition();
const [comments, setComments] = useState<Comment[]>([
{ id: 1, text: 'Really awesomwe!' },
{ id: 2, text: 'I love it 🧡' },
]);
const handleAddComment = async (formdata: FormData) => {
const messageText = formdata.get('post-message');
addOptimisticComment(messageText);
startTransition(async() => {
await new Promise((resolve) => setTimeout(resolve, 3000));
setComments((prev) => [
...prev,
{
id: new Date().getTime(),
text: messageText,
},
]);
});
};
const [optimisticComments, addOptimisticComment] = useOptimistic(comments, (currentComments, newCommentText: string) => {
return [...currentComments, {
id: new Date().getTime(),
text: newCommentText,
optimistic: true,
}]
});
return (
<>
{/* map optimisticComments instead comments */}
{/* ... */}
{/* form */}
<form
action={event => handleAddComment(event)}
className="flex flex-col items-center justify-center bg-gray-300 w-[500px] rounded-b-3xl p-4"
>
<input
type="text"
name="post-message"
placeholder="Wite a comment"
required
className="w-full p-2 rounded-md mb-2 text-black bg-white"
/>
<button
type="submit"
// replace previous false to wait for startTransition action
disabled={isPending}
className="bg-blue-500 disabled:bg-gray-400
text-white p-2 rounded-md w-full">
Send
</button>
</form>
</>
)