Cheatsheet React
Biblioteca JavaScript para interfaces
React
Components
Functional component
function Hello() {
return <h1>Hello World!</h1>;
}
export default Hello;A functional component is a JavaScript function that returns JSX. It is the standard way to create components in modern React.
Component composition
function App() {
return (
<div>
<Header />
<Main />
<Footer />
</div>
);
}Composing is building the interface by combining smaller components inside others. Each component stays simple and reusable.
Props spread
const props = { name: "Anna", age: 30 };
// Pass all props at once:
<Profile {...props} />
// Equivalent to:
<Profile name="Anna" age={30} />The spread (...) spreads an object into individual props. Useful for passing props along without listing them one by one.
Render with &&
function Notifications({ count }) {
return (
<div>
<h1>Messages</h1>
{count > 0 && (
<span className="badge">{count}</span>
)}
</div>
);
}The && operator renders the right side only if the condition is true. Shorter than a ternary when there is no alternative.
Component with arrow function
const Hello = () => <h1>Hello World!</h1>; export default Hello;
A shorter version using an arrow function. Widely used for simple one-line components.
Attributes in JSX
<input
type="text"
placeholder="Name"
disabled={true}
tabIndex={1}
data-id="field-name"
/>Attributes follow the HTML pattern, but with camelCase names (tabIndex) and dynamic values inside { }.
Component the a variable
function App({ type }) {
const Component = type === "admin"
? PainelAdmin
: PainelUser;
return <Component />;
}You can store a component in a variable (capitalized) and render it dynamically. The name must start with an uppercase letter.
Pure component (memo)
const Item = React.memo(function Item({ name }) {
console.log("renderizou:", name);
return <li>{name}</li>;
});
// Only re-renders if "name" changes
// Shallow comparison of propsA pure component (React.memo) only re-renders when the props change. It avoids unnecessary renders in large lists.
Basic JSX
function Cartao() {
return (
<div className="cartao">
<h2>Title</h2>
<p>Content here</p>
<img src="/photo.jpg" alt="Photo" />
</div>
);
}JSX looks like HTML, but uses className instead of class. A component must return a single root element.
Render to the DOM
import { createRoot } from "react-dom/client";
import App from "./App";
const root = createRoot(
document.getElementById("root")
);
root.render(<App />);createRoot connects React to an HTML element (usually a div#root). render draws the main component on the page.
Early return
function Profile({ user }) {
if (!user) return <p>Faz login</p>;
if (user.blocked) return <p>Account suspensa</p>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}The early return handles special cases at the start with an early return. The main code stays clean, without nested ternaries.
Fragment
function Line() {
return (
<>
<td>Name</td>
<td>Value</td>
</>
);
}
// Equivalent:
import { Fragment } from "react";
return <Fragment>...</Fragment>;The Fragment (<>...</>) groups elements without creating an extra node in the DOM. Essential when a component needs to return multiple siblings.
Expressions in JSX
function Profile({ name, age }) {
return (
<div>
<p>{name}</p>
<p>{age + 1} years</p>
<p>{"text".toUpperCase()}</p>
</div>
);
}Use { } to insert any JavaScript expression inside JSX: variables, calculations or function calls.
Comments in JSX
function Cartao() {
return (
<div>
{/* This is a JSX comment */}
<h2>Title</h2>
{/* <p>Text removed</p> */}
</div>
);
}Inside JSX, comments use {/* ... */}. Outside JSX (in JavaScript), use // the usual.
Props with TypeScript
interface Props {
name: string;
age?: number; // optional
onSave: (id: number) => void;
}
function Profile({ name, age = 0, onSave }: Props) {
return <p>{name}, {age}</p>;
}With TypeScript, you define an interface for the props. The compiler warns you if a required prop is missing or the type is wrong.
Children prop
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="body">{children}</div>
</div>
);
}
// Usage - the content between the tags becomes children:
<Card title="Profile">
<p>Name: Anna</p>
<button>Edit</button>
</Card>The children prop receives everything placed between the opening and closing tags of the component. Ideal for wrappers like cards, modals and layouts.
Props and Status
Props
function Greeting({ name, age }) {
return <p>Hello {name}, {age} years</p>;
}
<Greeting name="Anna" age={30} />Props are the data passed from a parent component to a child. They work like the arguments of a function.
State with an array
const [items, setItens] = useState([]);
// Add:
setItens([...items, novoItem]);
// Remove:
setItens(items.filter(i => i.id !== id));
// Update:
setItens(items.map(i =>
i.id === id ? { ...i, name: "new" } : i
));State arrays are updated immutably: create a new array with filter, map or spread, instead of altering the original.
Asynchronous update
const [count, setCount] = useState(0); // ❌ Uses the old value: setCount(count + 1); setCount(count + 1); // +1, not +2! // ✅ Functional form (correct): setCount(prev => prev + 1); setCount(prev => prev + 1); // +2 ✓
State is asynchronous: within the same event, the value does not change immediately. Use the functional form prev => for chained updates.
Controlled vs Uncontrolled
// Controlled: React controls the value
const [value, setValue] = useState("");
<input value={value} onChange={e => setValue(e.target.value)} />
// Uncontrolled: DOM controls it (with a ref)
const ref = useRef();
<input ref={ref} defaultValue="text" />
// Read: ref.current.valueIn controlled, React is the source of truth (value + onChange). In uncontrolled, the DOM stores the value and you read it with a ref. Prefer controlled.
State vs Props
function ContadorPai() {
const [total, setTotal] = useState(0); // STATE (owner)
return (
<>
<Display value={total} /> {/* PROP (read-only) */}
<button onClick={() => setTotal(total + 1)}>
+1
</button>
</>
);
}
function Display({ value }) {
// value is a PROP - it cannot be modified here
return <p>Total: {value}</p>;
}State (useState) is data the component controls and can change. Props are data received from the parent, read-only. The owner of the state passes it down the a prop.
Props are read-only
function Button({ text }) {
// text = "new"; ❌ Error!
return <button>{text}</button>;
}A prop should never be modified inside the component that receives it. For data that changes, use state.
Lifting state up
function Pai() {
const [value, setValue] = useState("");
return (
<Child value={value} onChange={setValue} />
);
}
function Child({ value, onChange }) {
return (
<input
value={value}
onChange={e => onChange(e.target.value)}
/>
);
}When two components need the same state, lift it up to the common parent and pass it down via props.
Derived state
const [items, setItens] = useState([]); // ✅ Calculate during render: const total = items.reduce((s, i) => s + i.price, 0); const ativos = items.filter(i => i.active); // ❌ Don't store in state: // const [total, setTotal] = useState(0);
If a value can be calculated from existing props or state, don't store it in separate state. Calculate it directly during render.
Callback props
// The parent passes a function the a prop:
function Pai() {
const [items, setItens] = useState([]);
return (
<Child onAdd={(item) =>
setItens(prev => [...prev, item])
} />
);
}
// The child calls the callback:
function Child({ onAdd }) {
return (
<button onClick={() => onAdd("new")}>
Add
</button>
);
}The callback props pattern lets the child communicate with the parent. The child calls the function and the parent decides what to do with the data.
useState
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Click: {count}
</button>
);
}useState creates a state variable and the function to update it. When the state changes, the component re-renders.
Props destructuring
// Without destructuring:
function Profile(props) {
return <p>{props.name}, {props.age}</p>;
}
// With destructuring (cleaner):
function Profile({ name, age }) {
return <p>{name}, {age}</p>;
}Destructuring extracts the props directly in the parameters. It makes the code shorter and more readable, especially with many props.
Immutability
const [user, setUser] = useState({ name: "Anna", age: 30 });
// ❌ Mutates the object (no re-render!)
user.name = "Ray";
setUser(user);
// ✅ Creates a new object
setUser({ ...user, name: "Ray" });
// ✅ Array: new array
setItens([...items, new]);React only re-renders if the reference changes. Mutate the object and pass the same reference = nothing happens. Always create a new one with spread.
Default props
function Button({ color = "blue", size = "md", children }) {
return (
<button className={`btn btn-${color} btn-${size}`}>
{children}
</button>
);
}
// Usage:
<Button>Default</Button> // blue, md
<Button color="red">Error</Button> // red, mdUse destructuring with defaults to set default values on the props. The component works even without passing all the props.
State with an object
const [form, setForm] = useState({
name: "",
email: ""
});
setForm(prev => ({
...prev,
name: "Anna"
}));For objects, use the spread (...) to copy the existing fields and update only some of them. Never mutate the object directly.
Prop drilling
// The theme passes through several levels without being used:
function App() {
const [theme, setTheme] = useState("dark");
return <Panel theme={theme} />;
}
function Panel({ theme }) {
return <Button theme={theme} />; // just passes it on
}
function Button({ theme }) {
return <button className={theme} />;
}Prop drilling is passing props through several components that only pass them on. For many levels, use Context (see Advanced Patterns).
State with a function (lazy init)
// ❌ Calculates on every render:
const [data, setDados] = useState(
JSON.parse(localStorage.getItem("x"))
);
// ✅ Calculates only once:
const [data, setDados] = useState(
() => JSON.parse(localStorage.getItem("x"))
);Pass a function to useState when the initial value is expensive to compute. React only runs it on the first render (lazy initialization).
useReducer (complex state)
import { useReducer } from "react";
const initial = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, count: state.count + state.step };
case "reset":
return initial;
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initial);
return (
<button onClick={() => dispatch({ type: "increment" })}>
{state.count}
</button>
);
}useReducer is an alternative to useState for state with complex logic. A reducer centralizes the transitions and dispatch sends actions.
Hooks
useEffect
import { useEffect } from "react";
useEffect(() => {
document.title = `Cliques: ${count}`;
return () => {
// cleanup (optional)
};
}, [count]);useEffect runs code after the render. The dependency array controls when it runs again.
useRef
import { useRef } from "react";
function Foco() {
const inputRef = useRef(null);
return (
<>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>
Focus
</button>
</>
);
}useRef stores a persistent value that does not cause a re-render. Widely used to access DOM elements directly.
useDeferredValue
import { useDeferredValue } from "react";
function Search({ query }) {
const deferred = useDeferredValue(query);
const results = useMemo(
() => list.filter(i => i.includes(deferred)),
[deferred]
);
return <List items={results} />;
}useDeferredValue defers a value's update, keeping the UI responsive. The list updates right away, the results come later.
useEffect with AbortController
useEffect(() => {
const controller = new AbortController();
fetch("/api/data", {
signal: controller.signal,
})
.then(r => r.json())
.then(setDados)
.catch(err => {
if (err.name !== "AbortError") throw err;
});
return () => controller.abort();
}, [id]);The AbortController cancels pending requests when the component unmounts or the dependency changes. It prevents memory leaks and race conditions.
useSyncExternalStore
import { useSyncExternalStore } from "react";
// Subscribe to an external store (e.g. localStorage):
function useLocalStorage(key) {
return useSyncExternalStore(
(callback) => {
window.addEventListener("storage", callback);
return () => window.removeEventListener("storage", callback);
},
() => localStorage.getItem(key)
);
}
// Usage:
function Profile() {
const name = useLocalStorage("name");
return <p>Hello, {name}</p>;
}useSyncExternalStore subscribes to data sources external to React (stores, browser APIs). It guarantees consistent reads without tearing.
useEffect: fetch data
const [data, setDados] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/products")
.then(r => r.json())
.then(data => {
setDados(data);
setLoading(false);
});
}, []);With an empty [] dependency array, the effect runs only once, when the component mounts. Ideal for loading data.
useMemo
import { useMemo } from "react";
const total = useMemo(
() => items.reduce((s, i) => s + i.price, 0),
[items]
);useMemo memoizes the result of an expensive calculation and only recalculates it when the dependencies change. It avoids repeated work.
useImperativeHandle
const Modal = forwardRef((props, ref) => {
const [open, setAberto] = useState(false);
useImperativeHandle(ref, () => ({
open: () => setAberto(true),
close: () => setAberto(false),
}));
return open ? <div>Modal</div> : null;
});
// In the parent: modalRef.current.open();useImperativeHandle exposes custom methods of the child to the parent via a ref. The parent calls ref.current.open() directly.
Custom hook: useFetch
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then(r => r.json())
.then(d => { setData(d); setLoading(false); })
.catch(e => { setError(e); setLoading(false); });
}, [url]);
return { data, loading, error };
}
// Usage: const { data, loading } = useFetch("/api/x");A useFetch encapsulates the data fetching pattern. It returns data, loading and error. Reusable in any component.
Rules of Hooks
// CORRECT - at the top of the component:
function App() {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
useEffect(() => { }, []);
// ...
}
// WRONG - inside a condition:
function App() {
if (logado) {
const [x, setX] = useState(0); // ❌ ERROR
}
for (...) {
useEffect(() => { }); // ❌ ERROR
}
}Hooks must be called always in the same order, at the top of the component. Never inside conditions, loops or nested functions. React relies on the call order.
useEffect: dependencies
// Runs on every render:
useEffect(() => { ... });
// Runs only on mount:
useEffect(() => { ... }, []);
// Runs when "id" changes:
useEffect(() => { ... }, [id]);The dependency array decides when the effect runs: with no array it always runs, empty it runs once, with values it runs when those values change.
useCallback
import { useCallback } from "react";
const handleClick = useCallback(
(id) => setSelecionado(id),
[]
);useCallback memoizes a function so its reference stays stable between renders. Useful when passing functions to optimized children.
useEffect vs useLayoutEffect
// useEffect: after the browser paints (asynchronous)
useEffect(() => {
console.log("after of the parentnt");
}, []);
// useLayoutEffect: before the paint (synchronous)
useLayoutEffect(() => {
// Measure the DOM before the user sees it
const rect = ref.current.getBoundingClientRect();
setPosition(rect.top);
}, []);useLayoutEffect runs before the browser paints. Use it for DOM measurements that need to be ready before the visual render.
useId
import { useId } from "react";
function Field({ label }) {
const id = useId();
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} />
</>
);
}
// Each instance generates a unique ID: ":r1:", ":r2:"...useId() generates unique and stable IDs for accessibility (label + input). Never use Math.random() for accessibility IDs.
The use() hook (React 19)
import { use, Suspense } from "react";
// use() reads a promise inside the render:
function Profile({ userPromise }) {
const user = use(userPromise); // suspends until it resolves
return <p>{user.name}</p>;
}
// Wrap it with Suspense:
<Suspense fallback={<Spinner />}>
<Profile userPromise={fetchUser()} />
</Suspense>use() (React 19) reads a promise's value directly in the render, suspending the component until it resolves. Unlike useContext, it can be called inside conditions.
useEffect: cleanup
useEffect(() => {
const timer = setInterval(() => {
setSegundos(s => s + 1);
}, 1000);
return () => clearInterval(timer);
}, []);The returned function does the cleanup before the component unmounts or the effect re-runs. Essential for timers, listeners and subscriptions.
Custom Hook
function useLocalStorage(key, initial) {
const [value, setValue] = useState(
() => JSON.parse(localStorage.getItem(key)) ?? initial
);
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}A custom hook is a function that starts with use and reuses stateful logic across multiple components.
useDebugValue
function useOnline() {
const [online, setOnline] = useState(navigator.onLine);
useDebugValue(online ? "Online" : "Offline");
return online;
}
// Visible in React DevTools:
// useOnline: "Online"useDebugValue shows a custom value in React DevTools for custom hooks. It makes debugging easier without console.log.
useTransition
import { useTransition, useState } from "react";
function Search() {
const [isPending, startTransition] = useTransition();
const [results, setResultados] = useState([]);
const search = (text) => {
startTransition(() => {
setResultados(filter(text)); // does not block the input
});
};
return (
<>
<input onChange={e => search(e.target.value)} />
{isPending ? <Spinner /> : <List items={results} />}
</>
);
}useTransition marks updates the non-urgent. The input stays responsive while the heavy list updates in the background.
useOptimistic (React 19)
import { useOptimistic } from "react";
function Likes({ total, like }) {
const [otimista, setOtimista] = useOptimistic(total);
async function handleClick() {
setOtimista(otimista + 1); // shows immediately, before the response
await like(); // the real state comes from the server
}
return (
<button onClick={handleClick}>
Gosto: {otimista}
</button>
);
}useOptimistic (React 19) immediately shows the expected result, before the server responds. If the request fails, the value automatically reverts to the real state.
Events and Forms
Event handlers
function Button() {
const handleClick = (e) => {
e.preventDefault();
console.log("clicado!");
};
return (
<button onClick={handleClick}>
Click
</button>
);
}Events use camelCase names (onClick, onChange, onSubmit) and receive a function, not a string.
Checkbox
const [aceite, setAceite] = useState(false);
<label>
<input
type="checkbox"
checked={aceite}
onChange={e => setAceite(e.target.checked)}
/>
I accept the terms
</label>On the checkbox use checked (not value) and read e.target.checked to know whether it was checked or unchecked.
File upload
function Upload() {
const [file, setFile] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("file", file);
await fetch("/api/upload", {
method: "POST",
body: formData,
});
};
return (
<form onSubmit={handleSubmit}>
<input type="file"
onChange={e => setFile(e.target.files[0])} />
<button>Send</button>
</form>
);
}For upload, use FormData and the input type="file". Don't set Content-Type — the browser sets the multipart automatically.
Multiple inputs with name
function Form() {
const [data, setDados] = useState({
name: "", email: "", city: ""
});
const handleChange = (e) => {
const { name, value } = e.target;
setDados(prev => ({ ...prev, [name]: value }));
};
return (
<>
<input name="name" value={data.name} onChange={handleChange} />
<input name="email" value={data.email} onChange={handleChange} />
<input name="city" value={data.city} onChange={handleChange} />
</>
);
}A single handler handles all inputs using the name attribute. The computed property [name] updates the right field dynamically.
Controlled input
function Form() {
const [name, setName] = useState("");
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
);
}In a controlled input, the value comes from state and each keystroke updates it. React is the single source of truth.
Radio buttons
const [option, setOption] = useState("a");
<label>
<input
type="radio"
value="a"
checked={option === "a"}
onChange={e => setOption(e.target.value)}
/>
Option A
</label>Each radio has a value and becomes checked when it matches the state. The onChange stores the chosen value.
Real-time validation
const [email, setEmail] = useState("");
const [error, setErro] = useState("");
const handleChange = (e) => {
const value = e.target.value;
setEmail(value);
if (value && !value.includes("@")) {
setErro("Email invalid");
} else {
setErro("");
}
};
<input value={email} onChange={handleChange} />
{error && <span className="error">{error}</span>}Real-time validation checks on each keystroke. It shows the error immediately and clears it when the input becomes valid.
Controlled select and textarea
function Form() {
const [country, setPais] = useState("pt");
const [msg, setMsg] = useState("");
return (
<>
<select value={country} onChange={e => setPais(e.target.value)}>
<option value="pt">Portugal</option>
<option value="br">Brasil</option>
<option value="ao">Angola</option>
</select>
<textarea
value={msg}
onChange={e => setMsg(e.target.value)}
rows={4}
/>
</>
);
}select and textarea are also controlled with value + onChange. In the select, the value matches the value attribute of the selected option.
Form with submit
function FormLogin() {
const [email, setEmail] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
console.log({ email });
};
return (
<form onSubmit={handleSubmit}>
<input
value={email}
onChange={e => setEmail(e.target.value)}
/>
<button type="submit">Enter</button>
</form>
);
}Use onSubmit on the form and e.preventDefault() to prevent the page from reloading. Read the values from state.
Keyboard events
<input
onKeyDown={(e) => {
if (e.key === "Enter") {
search();
}
}}
/>
// e.key: the pressed key
// e.code: the physical key codeThe onKeyDown/onKeyUp events detect keys. Use e.key to know which one was pressed (e.g. "Enter").
Form with reset
const initial = { name: "", email: "" };
const [form, setForm] = useState(initial);
const handleSubmit = async (e) => {
e.preventDefault();
await fetch("/api", {
method: "POST",
body: JSON.stringify(form),
});
setForm(initial); // clear
};
<form onSubmit={handleSubmit}>
<input value={form.name}
onChange={e => setForm({ ...form, name: e.target.value })} />
<button>Send</button>
</form>To clear the form after the submit, just reset the state to the initial object. The controlled inputs update automatically.
Actions: useActionState (React 19)
import { useActionState } from "react";
import { useFormStatus } from "react-dom";
async function create(estadoAnterior, formData) {
await fetch("/api/tasks", {
method: "POST",
body: formData,
});
return { ok: true };
}
function Button() {
const { pending } = useFormStatus();
return (
<button disabled={pending}>
{pending ? "A save..." : "Save"}
</button>
);
}
function Form() {
const [state, action] = useActionState(create, null);
return (
<form action={action}>
<input name="title" />
<Button />
</form>
);
}With Actions (React 19), the form uses action={fn} and submits without manual handlers. useActionState stores the result and useFormStatus exposes the pending state — no useState or setLoading.
Pass arguments
<button onClick={() => delete(item.id)}>
Remove
</button>
<li onMouseEnter={() => setHover(id)}>
{name}
</li>To pass parameters to the handler, wrap it in an arrow function. That way you control exactly what is sent.
Debounce in search
const [text, setTexto] = useState("");
const [results, setResultados] = useState([]);
useEffect(() => {
const timer = setTimeout(() => {
fetch(`/api?q=${text}`)
.then(r => r.json())
.then(setResultados);
}, 300);
return () => clearTimeout(timer);
}, [text]);Debounce waits for the user to stop typing before searching. The cleanup cancels the previous timer on each keystroke.
Submit with Enter
function Chat() {
const [msg, setMsg] = useState("");
const send = () => {
if (!msg.trim()) return;
console.log("Shipped:", msg);
setMsg("");
};
return (
<input
value={msg}
onChange={e => setMsg(e.target.value)}
onKeyDown={e => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
send();
}
}}
/>
);
}Use onKeyDown to detect the Enter key. Check e.key === "Enter" and prevent the default to avoid inserting a new line.
Lists and Conditionals
Render a list
function ListaProdutos({ products }) {
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}Use map to transform each array item into a JSX element. React renders the full list.
Empty list
{items.length === 0 ? (
<p>None item found.</p>
) : (
<ul>
{items.map(i => <li key={i.id}>{i.name}</li>)}
</ul>
)}Always handle the empty state with a friendly message, instead of showing a blank list.
Sorting a list
const [order, setOrdem] = useState("asc");
const sorted = useMemo(() => {
return [...items].sort((a, b) =>
order === "asc"
? a.name.localeCompare(b.name)
: b.name.localeCompare(a.name)
);
}, [items, order]);
<button onClick={() =>
setOrdem(o => o === "asc" ? "desc" : "asc")
}>
Sort: {order}
</button>Create a copy with [...items] before sorting (sort mutates the original). The useMemo avoids re-sorting on every render.
Infinite scroll
const [page, setPagina] = useState(1);
const [items, setItens] = useState([]);
useEffect(() => {
fetch(`/api/items?page=${page}`)
.then(r => r.json())
.then(newItems => setItens(prev => [...prev, ...newItems]));
}, [page]);
// Detect the end of the page:
const onScroll = () => {
if (window.innerHeight + window.scrollY >=
document.body.offsetHeight - 200) {
setPagina(p => p + 1);
}
};
window.addEventListener("scroll", onScroll);Infinite scroll loads more data when the user reaches the end. It detects the scroll position and increments the page.
The importance of key
// ✅ Correct: unique and stable id
{items.map(i => <li key={i.id}>{i.name}</li>)}
// ❌ Avoid: using the index
{items.map((i, idx) => <li key={idx}>{i.name}</li>)}The key uniquely identifies each item. Using the index can cause bugs when the list is reordered or filtered.
Content variable
let content;
if (loading) {
content = <Spinner />;
} else if (error) {
content = <p>Error ao load</p>;
} else {
content = <Data data={data} />;
}
return <div>{content}</div>;For conditions with several branches, assign the JSX to a variable with if/else and render it at the end. More readable than nested ternaries.
Simple pagination
const [page, setPagina] = useState(1);
const porPagina = 10;
const start = (page - 1) * porPagina;
const visible = items.slice(start, start + porPagina);
const totalPaginas = Math.ceil(items.length / porPagina);
return (
<>
{visible.map(i => <Cartao key={i.id} {...i} />)}
<button disabled={page === 1}
onClick={() => setPagina(p => p - 1)}>Previous</button>
<span>{page} / {totalPaginas}</span>
<button disabled={page === totalPaginas}
onClick={() => setPagina(p => p + 1)}>Next</button>
</>
);Pagination shows only a slice of the data with slice. The previous/next buttons control the current page.
Grouped list
const groups = items.reduce((acc, item) => {
(acc[item.category] ??= []).push(item);
return acc;
}, {});
return (
<div>
{Object.entries(groups).map(([cat, list]) => (
<section key={cat}>
<h3>{cat}</h3>
<ul>
{list.map(i => <li key={i.id}>{i.name}</li>)}
</ul>
</section>
))}
</div>
);Use reduce() to group items by category and then Object.entries() to iterate the groups with their items.
Conditional rendering
// Ternary:
{logado ? <Panel /> : <Login />}
// && (short-circuit):
{errors.length > 0 && (
<div className="error">{errors[0]}</div>
)}The ternary picks between two options. The && operator shows something only when the condition is true.
Nested lists
{categories.map(cat => (
<div key={cat.id}>
<h3>{cat.name}</h3>
<ul>
{cat.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
))}For lists inside lists, do a nested map. Each level needs its own unique key.
Real-time filter
const [search, setBusca] = useState("");
const filtrados = useMemo(() => {
if (!search) return items;
const term = search.toLowerCase();
return items.filter(i =>
i.name.toLowerCase().includes(term)
);
}, [items, search]);
return (
<>
<input value={search}
onChange={e => setBusca(e.target.value)}
placeholder="Search..." />
{filtrados.map(i => <Cartao key={i.id} {...i} />)}
</>
);The real-time filter uses useMemo to recompute only when the search or the items change. The list updates on every keystroke.
Conditionals with an object
const icons = {
success: "✅",
error: "❌",
warning: "⚠️",
info: "ℹ️",
};
function Alert({ type, msg }) {
return (
<div className={`alert-${type}`}>
<span>{icons[type] ?? "❓"}</span>
{msg}
</div>
);
}
// Usage:
<Alert type="success" msg="Saved!" />A lookup object replaces long if/else or switch chains. The ?? operator provides a fallback for missing keys.
Filter and transform
const ativos = products
.filter(p => p.active)
.map(p => (
<Cartao key={p.id} product={p} />
));
return <div>{ativos}</div>;Combine filter (to select) with map (to render). First you filter, then you transform into components.
Ternary vs &&
// Ternary: two possible results
{admin ? <PainelAdmin /> : <PainelUser />}
// &&: shows only if true
{errors.length > 0 && <Alert errors={errors} />}
// Careful: 0 && <X /> renders "0"!
{count > 0 && <p>{count} items</p>}Use a ternary when there are two options and && to show or hide. Watch out: 0 && ... shows the zero — check with > 0.
Virtualization (react-window)
import { FixedSizeList } from "react-window";
<FixedSizeList
height={400}
itemCount={10000}
itemSize={35}
width="100%"
>
{({ index, style }) => (
<div style={style}>Item {index}</div>
)}
</FixedSizeList>Virtualization renders only the items visible in the viewport. With 10,000 items, only ~20 are in the DOM. Essential for huge lists.
Styles and CSS
Inline styles
const style = {
color: "white",
backgroundColor: "blue",
padding: "1rem",
borderRadius: "8px"
};
<div style={style}>Hello</div>
<p style={{ fontSize: 14 }}>Text</p>The style attribute takes a JavaScript object with properties in camelCase. Good for simple dynamic styles.
CSS variables (theme)
/* theme.css */
:root {
--color-primaria: #3b82f6;
--spacing: 1rem;
}
/* component */
.cartao {
color: var(--color-primaria);
padding: var(--spacing);
}CSS variables (--name) centralize colors and spacing. Changing the theme is the simple the swapping the value at the root.
CSS-in-JS (styled-components)
import styled from "styled-components";
const Button = styled.button`
background: ${props => props.$primario ? "#0070f3" : "#eee"};
color: ${props => props.$primario ? "#fff" : "#333"};
padding: 8px 16px;
border-radius: 6px;
border: none;
`;
// Usage:
<Button $primario>Save</Button>
<Button>Cancel</Button>styled-components lets you write real CSS inside JS. The dynamic props generate conditional styles without classes.
CSS Modules
/* Button.module.css */
.button { padding: 8px 16px; }
.primario { background: blue; }import styles from "./Button.module.css";
<button className={styles.button}>Click</button>CSS Modules give each class a unique name, avoiding global conflicts. The style is scoped locally to the component.
CSS animations
/* styles.css */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.cartao {
animation: fadeIn 0.3s ease-out;
}// In the component: <div className="cartao">Content</div>
CSS animations (@keyframes) give smooth transitions without JavaScript. Apply the class to the element and the browser animates automatically.
Tailwind CSS
function Card({ title, active }) {
return (
<div className={
"rounded-lg p-4 shadow " +
(active ? "bg-blue-500 text-white" : "bg-white text-gray-800")
}>
<h2 className="text-xl font-bold">{title}</h2>
<p className="mt-2 text-sm opacity-75">Content do card</p>
</div>
);
}
// With template literals:
<div className={`p-4 ${active ? "bg-blue-500" : "bg-white"}`}>Tailwind uses utility classes directly in the JSX. Combine with ternaries or template literals for conditional styles.
Conditional className
<div className={`cartao ${active ? "active" : ""}`}>
<button className={`btn ${
variante === "primary"
? "btn-primary"
: "btn-secondary"
}`}>Combine fixed and dynamic classes with template literals. The class is applied according to the condition.
clsx / classnames
import clsx from "clsx";
<button className={clsx(
"btn",
{
"btn-primary": variante === "primary",
"btn-lg": big,
"disabled": !active,
}
)}>
Click
</button>The clsx library joins classes conditionally in a clean way. It avoids complex template literals with multiple conditions.
Dynamic styles
function Barra({ progresso }) {
return (
<div
style={{
width: `${progresso}%`,
backgroundColor: progresso > 80 ? "green" : "orange"
}}
/>
);
}Compute styles from state or props. The style object is recreated on every render with the current values.
Dark mode toggle
const [theme, setTheme] = useState("light");
useEffect(() => {
document.documentElement.dataset.theme = theme;
}, [theme]);
<button onClick={() =>
setTheme(t => t === "light" ? "dark" : "light")
}>
{theme === "light" ? "🌙" : "☀️"}
</button>
/* CSS: */
[data-theme="dark"] { --background: #1a1a2e; --text: #eee; }
[data-theme="light"] { --background: #fff; --text: #333; }Dark mode toggles an attribute on the html and the CSS uses variables to swap the colors. The state stores the preference.
Advanced Standards
Context API
import { createContext, useContext } from "react";
const ThemeCtx = createContext("light");
// Provider:
<ThemeCtx.Provider value="dark">
<App />
</ThemeCtx.Provider>
// Consumer:
function Button() {
const theme = useContext(ThemeCtx);
return <button className={theme}>...</button>;
}The Context shares data with the whole component tree without passing props through every level (prop drilling).
forwardRef
import { forwardRef } from "react";
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
// In the parent:
const ref = useRef();
<Input ref={ref} />
ref.current.focus();forwardRef lets you pass a ref to an element inside a child component. Useful for focusing inputs or measuring elements.
Error Boundary
import { Component } from "react";
class ErrorBoundary extends Component {
state = { error: null };
static getDerivedStateFromError(error) {
return { error };
}
render() {
if (this.state.error) {
return <h1>Something went wrong: {this.state.error.message}</h1>;
}
return this.props.children;
}
}
// Usage:
<ErrorBoundary>
<ComponentThatCanFail />
</ErrorBoundary>An Error Boundary catches rendering errors in the children and shows a fallback. It still requires a class component (the only case).
Split Context (performance)
// ❌ One giant context re-renders everyone:
<Ctx.Provider value={{ user, theme, dispatch }}>
// ✅ Separate contexts only re-render those who use them:
<UserCtx.Provider value={user}>
<TemaCtx.Provider value={theme}>
<App />
</TemaCtx.Provider>
</UserCtx.Provider>
// Or memoize the value:
<Ctx.Provider value={useMemo(
() => ({ user, dispatch }), [user]
)}>Changing a context re-renders all consumers. Split into small contexts or memoize the value with useMemo so each component only re-renders when the data it uses changes.
Simple global state
const AppCtx = createContext();
function AppProvider({ children }) {
const [state, dispatch] = useReducer(
reducer, estadoInicial
);
return (
<AppCtx.Provider value={{ state, dispatch }}>
{children}
</AppCtx.Provider>
);
}
// In any component:
const { state, dispatch } = useContext(AppCtx);Combine useContext + useReducer to create a simple global state, without external libraries.
Compound Components
function Tabs({ children }) {
const [active, setAtiva] = useState(0);
return (
<div>
{children.map((child, i) =>
cloneElement(child, {
active: i === active,
onClick: () => setAtiva(i),
})
)}
</div>
);
}
<Tabs>
<Tab label="Profile">...</Tab>
<Tab label="Settings">...</Tab>
</Tabs>In the Compound Components pattern, parent and child components share state implicitly. The API stays declarative and flexible.
Portal (createPortal)
import { createPortal } from "react-dom";
function Modal({ open, children, onClose }) {
if (!open) return null;
return createPortal(
<div className="overlay" onClick={onClose}>
<div className="modal" onClick={e => e.stopPropagation()}>
{children}
</div>
</div>,
document.body // renders outside the tree
);
}createPortal renders children into a different DOM node (e.g. body). Ideal for modals, tooltips and dropdowns that need to escape overflow.
Lazy loading
import { lazy, Suspense } from "react";
const Panel = lazy(() => import("./Panel"));
function App() {
return (
<Suspense fallback={<Spinner />}>
<Panel />
</Suspense>
);
}lazy loads the component only when it's needed (code-splitting). Suspense shows a fallback during loading.
Slot pattern (children)
function Layout({ header, sidebar, children }) {
return (
<div className="layout">
<header>{header}</header>
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}
<Layout
header={<Nav />}
sidebar={<Menu />}
>
<Page />
</Layout>The slot pattern uses props to inject JSX into specific areas of the layout. More explicit than children for multiple areas.
Higher-Order Component (HOC)
function comAuth(Component) {
return function ComponenteProtegido(props) {
const { user } = useAuth();
if (!user) {
return <p>Do login to continue.</p>;
}
return <Component {...props} user={user} />;
};
}
// Usage:
const PainelProtegido = comAuth(Panel);
// In the router:
<PainelProtegido />An HOC is a function that receives a component and returns a new one with extra behavior. Useful for authentication, logging or prop injection.
Render props
function Data({ url, children }) {
const [data, setDados] = useState(null);
useEffect(() => {
fetch(url).then(r => r.json()).then(setDados);
}, [url]);
return children(data);
}
<Data url="/api/user">
{(user) => <p>{user?.name}</p>}
</Data>In the render props pattern, children is a function that receives data from the component. It inverts control in a flexible way.
Suspense for data
// With React Query / SWR:
function Products() {
const { data, isLoading } = useQuery(
["products"],
() => fetch("/api/products").then(r => r.json())
);
if (isLoading) return <Spinner />;
return <List items={data} />;
}
// Or with native Suspense (React 19+):
<Suspense fallback={<Spinner />}>
<Products />
</Suspense>Libraries like React Query or SWR manage cache, loading and errors. Suspense shows the fallback while the data loads.
ref the a prop (React 19)
// React 19: ref is a normal prop (no forwardRef):
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// In the parent:
const ref = useRef();
<Input ref={ref} />
ref.current.focus();
// In React 18 you needed:
// const Input = forwardRef((props, ref) => ...);In React 19, ref becomes a normal prop of function components — forwardRef is no longer needed. Just receive { ref } and apply it to the element.
Ecosystem and CLI
Create a project (Vite)
npm create vite@latest my-app -- --template react cd my-app npm install npm run dev
Vite creates a React project with instant hot reload. It's the recommended way to start a new project.
Folder structure
src/ ├── components/ # Reusable components ├── pages/ # Pages per route ├── hooks/ # Custom hooks ├── services/ # API calls ├── utils/ # Utility functions └── App.jsx
An organized structure by responsibility makes it easier to find and maintain the code the the project grows.
Deploy (build)
# Production build npm run build # Local preview of the build npm run preview # The output goes to dist/ # Deploy to: Vercel, Netlify, Cloudflare Pages
The build generates optimized files (minified, with hash) in the dist/ folder. Any static server or CDN serves them.
TanStack Router
import { createRouter, RouterProvider } from "@tanstack/react-router";
const router = createRouter({
routes: [
{ path: "/", component: Home },
{ path: "/users/$id", component: UserDetail },
],
});
function App() {
return <RouterProvider router={router} />;
}
// In the component:
const { id } = useParams({ from: "/users/$id" });TanStack Router is type-safe by nature: routes, params and search are inferred automatically by TypeScript.
React Router
import { BrowserRouter, Routes, Route, Link }
from "react-router-dom";
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user/:id" element={<Profile />} />
</Routes>
</BrowserRouter>React Router manages navigation in a SPA. Define routes with Route and navigate with Link without reloading the page.
Best practices
• Small and focused components • Stable key in lists (do not use index) • State the low the possible • useMemo/useCallback only when necessary • Separate logic into custom hooks • TypeScript for typed props
Conventions that keep React code clean, fast and easy to maintain in the long run.
Next.js (basics)
npx create-next-app@latest my-app
// app/page.jsx (Server Component by default)
export default function Home() {
return <h1>Hello Next.js</h1>;
}
// app/api/data/route.js (API route)
export async function GET() {
return Response.json({ ok: true });
}
// File-based routing: app/about/page.jsx = /aboutNext.js is a React framework with file-based routing, SSR and API routes. Each folder in app/ is a route automatically.
Vitest (tests)
import { render, screen } from "@testing-library/react";
import { expect, test } from "vitest";
import Counter from "./Counter";
test("shows the counter", () => {
render(<Counter />);
expect(screen.getByText("0")).toBeInTheDocument();
});
test("increments on click", async () => {
render(<Counter />);
await userEvent.click(screen.getByRole("button"));
expect(screen.getByText("1")).toBeInTheDocument();
});
// Run: npx vitestVitest is the default Vite test runner. With the Testing Library, test the behavior (clicking, seeing text) instead of the implementation.
useParams and useNavigate
import { useParams, useNavigate }
from "react-router-dom";
function Profile() {
const { id } = useParams();
const navigate = useNavigate();
const back = () => navigate(-1);
const irHome = () => navigate("/");
}useParams reads URL parameters (:id). useNavigate lets you navigate programmatically.
Environment variables
// .env file VITE_API_URL=https://api.example.com // In the code (Vite): const url = import.meta.env.VITE_API_URL; // Create React App: const url = process.env.REACT_APP_API_URL;
Environment variables keep configuration (URLs, public keys) out of the code. In Vite, they start with VITE_.
Zustand (global state)
import { create } from "zustand";
const useStore = create((set) => ({
count: 0,
increment: () => set(s => ({ count: s.count + 1 })),
reset: () => set({ count: 0 }),
}));
// In any component:
function Counter() {
const { count, increment } = useStore();
return <button onClick={increment}>{count}</button>;
}Zustand is a minimalist alternative to Redux for global state. No providers, no boilerplate — a simple hook.
Server Components
// Server Component (default in Next.js App Router):
// Runs on the server and can access the DB directly:
async function Products() {
const products = await db.query(
"SELECT * FROM products"
);
return (
<ul>
{products.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}
// Client Component (needs hooks/events):
"use client";
function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
}Server Components run on the server and send ready-made HTML — ideal for fetching data without sending extra JavaScript. Use "use client" only on components that need hooks or events.
Fetch with async/await
function Products() {
const [data, setDados] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetch() {
const res = await fetch("/api/products");
const json = await res.json();
setDados(json);
setLoading(false);
}
fetch();
}, []);
if (loading) return <p>A load...</p>;
return <List items={data} />;
}A complete data fetching pattern: loading state, an async request in useEffect and conditional rendering.
Axios (fetch alternative)
import axios from "axios";
useEffect(() => {
axios.get("/api/products")
.then(res => setDados(res.data))
.catch(err => setErro(err.message));
}, []);
// POST:
axios.post("/api/user", { name: "Anna" });Axios is an HTTP request library. It gives the JSON already parsed in res.data and has a simpler API than fetch.
React Hook Form
import { useForm } from "react-hook-form";
function Login() {
const { register, handleSubmit, formState } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email", { required: true })} />
{formState.errors.email && <span>Required</span>}
<button>Enter</button>
</form>
);
}React Hook Form manages forms with performance (no re-render on every keystroke). The register connects the input and the declarative validation.
StrictMode
import { StrictMode } from "react";
root.render(
<StrictMode>
<App />
</StrictMode>
);
// In development, StrictMode:
// - renders everything twice (catches impure renders)
// - runs effects twice (catches effects without cleanup)
// In production it has no effect at all.StrictMode is a development tool that detects problems: it renders everything twice and runs effects extra times, exposing impure renders and effects without cleanup. It does not affect production.