React Js
React Js
Def! React (sau [Link]) este o bibliotecă JavaScript folosită pentru a construi
interfețe de utilizator (UI), în special pentru aplicații web moderne.
I. JSX
JSX (JavaScript XML) este o „extensie de sintaxă” pentru JavaScript folosită în
React care îți permite să scrii cod ce arată ca HTML direct în JavaScript.
JSX = JavaScript + HTML într-un singur loc
const element = <h1>Hello, world!</h1>;
Pentru a reda un element React, transmiteți mai întâi elementul DOM către
[Link](), apoi transmiteți elementul React către [Link]():
const root = [Link](
[Link]('root')
);
const element = <h1>Salut, lume</h1>;
[Link](element);
III. COMPONENTS
Def! Conceptual, componentele sunt ca funcțiile JavaScript. Acestea acceptă
intrări arbitrare (numite „props”) și returnează elemente React care descriu ce ar
trebui să apară pe ecran.
Componentele vă permit să împărțiți interfața utilizator în părți independente,
reutilizabile și să vă gândiți la fiecare parte în mod izolat. Această pagină oferă o
introducere în ideea de componente. Puteți găsi o referință detaliată a API-ului
componentelor aici.
Definirea componentelor
Cea mai simplă metodă de a defini o componentă este de a scrie o funcție
JavaScript:
function Welcome(props) {
return <h1>Hello, {[Link]}</h1>;
}
Această funcție este o componentă React validă deoarece acceptă un singur
argument obiect „props” (care reprezintă proprietăți) cu date și returnează un
element React. Numim astfel de componente „componente funcție” deoarece
sunt literalmente funcții JavaScript.
Randarea componentelor
De exemplu, acest cod redă „Hello, Sara” pe pagină:
function Welcome(props) {
return <h1>Hello, {[Link]}</h1>;
}
const root = [Link]([Link]('root'));
const element = <Welcome name="Sara" />;
[Link](element);
Apelăm [Link]() cu elementul <Welcome name="Sara" />. React apelează
componenta Welcome cu {name: 'Sara'} ca prop. Componenta noastră Welcome
returnează un element <h1>Hello, Sara</h1> ca rezultat.
DOM-ul React actualizează eficient DOM-ul pentru a se potrivi cu <h1>Hello,
Sara</h1>.
Notă: Începeți întotdeauna numele componentelor cu o literă mare.
React tratează componentele care încep cu litere mici ca etichete DOM. De
exemplu, <div /> reprezintă o etichetă div HTML, dar <Welcome /> reprezintă o
componentă și necesită ca Welcome să fie în domeniul de aplicare.
Component composition
Componentele se pot referi la alte componente în rezultatul lor. Acest lucru ne
permite să folosim aceeași abstractizare a componentelor pentru orice nivel de
detaliu. Un buton, un formular, un dialog, un ecran: în aplicațiile React, toate
acestea sunt exprimate în mod obișnuit ca componente.
function Welcome(props) {
return <h1>Hello, {[Link]}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
IV. PROPS
În React, props (prescurtare de la properties) sunt un obiect care conține datele
transmise unei componente din exterior.
Pe scurt, props = parametrii unei componente.
Caracteristici importante
V. useState
Def! useState este un hook din React care permite componentelor funcționale să
aibă și să gestioneze stare (state).
Sintaxa
const [state, setState] = useState(valoareInitiala);
state = valoarea curentă
setState = funcția care actualizează valoarea
Exemplu simplu
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</>
);
}
Cum funcționează
Idei importante
VI. useRef
Def: useRef este un hook React care vă permite să faceți referire la o valoare care
nu este necesară pentru randare.
Sintaxa: const ref = useRef(initialValue)
Returneaza
useRef returnează un obiect ref mutabil a cărui proprietate .current este
inițializată la argumentul transmis (initialValue). Obiectul returnat va persista pe
întreaga durată de viață a componentei.
useRef are doua roluri prinicipale:
function increment() {
[Link]++;
[Link]([Link]);
}
modificarea lui [Link] NU declanșează re-render
Caracteristici importante:
Persistă între render-uri -> Nu se resetează ca variabilele normale.
Nu provoacă re-render -> Foarte important pentru performanța
Este mutabil -> Poți modifica direct: [Link] = ceva;
Referință stabilă -> Obiectul ref rămâne același între render-uri
Exemplu comun:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
[Link]();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useRef din React este ca o „cutie” care stochează o valoare mutabilă în current și
persistă între render-uri.
Este folosit frecvent pentru accesarea elementelor DOM (prin ref), dar poate
stoca orice valoare modificabilă, similar câmpurilor de instanță din clase.
useImperativeHandle
Def! Un Context API este un mecanism (în special cunoscut din React) care îți
permite să partajezi date între componente fără să mai treci manual props prin
fiecare nivel al aplicației („prop drilling”).
Componentele principale:
creeateContext;
Provider;
Consumer;
useContext;
createContext
- creează un obiect de tip „context” care va ține datele globale.
- createContext() returnează un obiect cu 2 lucruri importante:
o Provider
o Consumer (mai vechi, rar folosit azi)
- daca nu exista Provider se foloseste un defaultValue;
- const MyContext = createContext(defaultValue);
Provider
- este sursa datelor;
- infasoara aplicatia si trimie datele la copii;
- react creeaza un canal intern prin care provider trimite datele si toate
componentele din interior le pot citi;
- <[Link] value={...}>
{children}
</[Link]>
function App() {
const [user, setUser] = useState(null);
return (
<[Link] value={{ user, setUser }}>
<Dashboard />
</[Link]>
);
}
useContext
- Permite unei componente să citească valoarea din context.
- const value = useContext(MyContext);
- caută cel mai apropiat Provider, ia value si-l returneaza
function Profile() {
const { user } = useContext(UserContext);
return <h1>{user}</h1>;
}
Consumer
Def! Consumer este partea „veche” din Context API în React care îți permite să
citești datele din context fără să folosești hook-uri.
Consumer este folosit pentru a accesa valoarea din context.
<[Link]>
{value => <div>{value}</div>}
</[Link]>
Consumer folosește un pattern numit „render prop”: (value) => (...)
Această funcție:
VIII. useReducer
Def! useReducer este un hook React care îți permite să adaugi un reducer
componentei tale. useReducer este o alternativă la useState pentru gestionarea
unui state mai complex.
Sintaxa
const [state, dispatch] = useReducer(reducer, initialState);
Parametri
reducer: Funcția reducer care specifică modul în care se actualizează starea.
Trebuie să fie pură, trebuie să ia starea și acțiunea ca argumente și trebuie să
returneze următoarea stare. Starea și acțiunea pot fi de orice tip.
initialState: Valoarea din care se calculează starea inițială. Poate fi o valoare de
orice tip. Modul în care se calculează starea inițială depinde de următorul
argument init.
opțional init: Funcția de inițializare care ar trebui să returneze starea inițială. Dacă
nu este specificată, starea inițială este setată la initialState. În caz contrar, starea
inițială este setată la rezultatul apelului init(initialState).
Returnează
useReducer returnează un array cu exact două valori:
Starea curentă. În timpul primei randări, este setată la init(initialState) sau
initialState (dacă nu există init).
Funcția de dispatch care vă permite să actualizați starea la o valoare diferită și să
declanșați o re-randare.
Exemplu complet
import { useReducer } from "react";
const initialState = { count: 0 };
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>{[Link]}</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>
+
</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>
-
</button>
</>
);}
IX. useEffect
Def! useEffect este un React Hook care vă permite să sincronizați o componentă
cu un sistem extern.
Sintaxa
useEffect(setup, dependencies?)
Parametrii
setup: Funcția cu logica Efectului. Funcția de configurare poate, de asemenea,
returna opțional o funcție de curățare. Când componenta se execută un commit,
React va rula funcția de configurare. După fiecare commit cu dependențe
modificate, React va rula mai întâi funcția de curățare (dacă ați furnizat-o) cu
valorile vechi, apoi va rula funcția de configurare cu noile valori. După ce
componenta este eliminată din DOM, React va rula funcția de curățare.
Returnează
useEffect returnează undefined.
Cu dependențe
useEffect(() => {[Link]("count s-a schimbat");}, [count]);
Se execută: la mount si ori de câte ori count se schimbă
Exemplu complet
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
// 1. Rulează o singură dată (la mount)
useEffect(() => {
[Link]("Component mounted");
return () => {
[Link]("Component unmounted");
};
}, []);
// 2. Rulează când count se schimbă
useEffect(() => {
[Link]("Count changed:", count);
}, [count]);
// 3. Interval (cu cleanup)
useEffect(() => {
const interval = setInterval(() => {
[Link]("Timer running...");
}, 2000);
return () => {
clearInterval(interval);
[Link]("Interval cleared");
};
}, []);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
export default App;
X. useCallback
Def! useCallback este un React Hook care vă permite să memorați în cache
definiția unei funcții între re-randări.
Sintaxa
const cachedFn = useCallback(fn, dependencies)
Parametri
fn: Valoarea funcției pe care doriți să o memorați în cache. Poate primi orice
argumente și poate returna orice valori. React va returna (nu va apela!) funcția
dvs. înapoi în timpul randării inițiale. La următoarele randări, React vă va oferi
aceeași funcție din nou dacă dependențele nu s-au modificat de la ultima
randare. În caz contrar, vă va oferi funcția pe care ați transmis-o în timpul randării
curente și o va stoca în cazul în care poate fi reutilizată ulterior. React nu va apela
funcția dvs. Funcția vă este returnată, astfel încât să puteți decide când și dacă o
apelați.
dependențe: Lista tuturor valorilor reactive la care se face referire în codul fn.
Valorile reactive includ prop-uri, stare și toate variabilele și funcțiile declarate
direct în corpul componentei. Dacă linter-ul dvs. este configurat pentru React,
acesta va verifica dacă fiecare valoare reactivă este specificată corect ca o
dependență. Lista de dependențe trebuie să aibă un număr constant de elemente
și să fie scrisă în linie, cum ar fi [dep1, dep2, dep3]. React va compara fiecare
dependență cu valoarea sa anterioară folosind algoritmul de comparație
[Link].
Returnează
La randarea inițială, useCallback returnează funcția fn pe care ați transmis-o.
În timpul randărilor ulterioare, va returna fie o funcție fn deja stocată de la ultima
randare (dacă dependențele nu s-au modificat), fie va returna funcția fn pe care
ați transmis-o în timpul acestei randări.
Exemplu complet
fara useCallback
function Parent() {
const [count, setCount] = useState(0);
const handleClick = () => {
[Link]("clicked");
};
return <Child onClick={handleClick} />;
}
cu useCallback
const handleClick = useCallback(() => {
[Link]("clicked");
}, []);
XI. FetchData
Exemplu simplu:
import { useEffect, useState } from "react";
function Users() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("[Link]
.then(response => [Link]())
.then(data => setUsers(data))
.catch(error => [Link](error));
}, []);
return (
<div>
<h1>Lista utilizatori</h1>
{[Link](user => (
<p key={[Link]}>{[Link]}</p>
))}
</div>
);
}
export default Users;
Exempu cu async/await
useEffect(() => {
async function fetchUsers() {
try {
const response = await fetch("[Link]
const data = await [Link]();
setUsers(data);
} catch (error) {
[Link](error);
}
}
fetchUsers();
}, []);
useEffect(() => {
async function fetchUsers() {
try {
const res = await fetch("[Link]
const data = await [Link]();
setUsers(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
fetchUsers();
}, []);
if (loading) return <p>Se încarcă...</p>;
if (error) return <p>Eroare!</p>;
Exemplu cu axios:
npm install axios
import axios from "axios";
useEffect(() => {
[Link]("[Link]
.then(res => setUsers([Link]))
.catch(err => [Link](err));
}, []);
useEffect(() => {
async function getData() {
try {
const res = await fetch(url);
const json = await [Link]();
setData(json);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
getData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
Cum se foloseste?
function Users() {
const { data, loading, error } = useFetch(
"[Link]
);
if (loading) return <p>Se încarcă...</p>;
if (error) return <p>Eroare!</p>;
return (
<div>
{[Link](user => (
<p key={[Link]}>{[Link]}</p>
))}
</div>
);
In React, hook-urile nu comunica direct intre ele. Ele returneaza valori si primesc
parametrii. Comunicatia se face prin props, return values sau context.
Transmiterea valorilor prin parametri și return
o Cel mai simplu mod: un hook primește valori ca parametru și
returnează valori. Apoi alt hook sau componentă folosește return-ul.
Folosirea unui hook comun pentru multiple componente sau hook-uri
o Poți crea un hook care gestionează state-ul, iar alte hook-uri sau
componente îl folosesc prin return.
Folosirea Context pentru valori globale între hook-uri
o Dacă mai multe hook-uri sau componente trebuie să acceseze
aceeași valoare, folosești React Context.
XIII. Memoization
Def! Memoizarea (memoization) este o tehnică de optimizare prin care salvezi
rezultatul unei funcții pentru anumite intrări (input-uri), astfel încât dacă
funcția este apelată din nou cu aceleași intrări, în loc să recalculeze rezultatul, îl
returnează din memorie.
Este un concept general din informatică, nu unul specific React. useMemo este
doar implementarea acestui concept în React.
function factorial(n) {
[Link]("Calculez...");
if (n <= 1) return 1;
return n * factorial(n - 1);
}
Cum se implementează
Fara memoizare:
function square(x) {
[Link]("Calculez");
return x * x;
}
square(5);
square(5);
square(5);
Output:
Calculez
Calculez
Calculez
Cu memoizare:
const cache = {};
function square(x) {
if (cache[x]) {
[Link]("Din cache");
return cache[x];
}
[Link]("Calculez");
const result = x * x;
cache[x] = result;
return result;
}
<Child />
</>
);
}
function Child() {
[Link]("Child render");
return <h1>Salut</h1>;
}
Primul render
React executa: App -> Child -> in consola: Child render.
return <h1>Salut</h1>;
});
Cum funcționează
Memorare (Caching): Când învelești o componentă în [Link], React îi
memorează rezultatul (JSX-ul) la prima randare.
Comparația Props-urilor: La următoarele randări, React va compara noile props
cu cele vechi.
Decizia de Randare: Dacă props-urile nu s-au schimbat, React va folosi
componenta din memorie, sărind complet peste procesul de re-randare.
XV. useMemo
Def! useMemo este un Hook din React folosit pentru a memora (memoize)
rezultatul unui calcul costisitor, astfel încât acel calcul să nu fie refăcut la fiecare
re-randare a componentei.
React:
execută funcția la primul render;
salvează rezultatul;
la render-urile următoare verifică dependențele;
dacă acestea NU s-au schimbat, returnează valoarea memorată;
dacă s-au schimbat, execută din nou funcția și salvează noul rezultat.
Exemplu:
function App() {
const [count, setCount] = useState(0);
const numbers = [1,2,3,4,5];
const sum = [Link]((a,b)=>a+b,0);
return (
<>
<h1>{count}</h1>
<p>{sum}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</>
);
}
La fiecare click:
componenta se re-randează;
reduce() se execută din nou.
În acest caz nu contează deoarece calculul este foarte mic.
Cu useMemo:
const sum = useMemo(() => {
return [Link]((a,b)=>a+b,0);
}, [numbers]);
Acum:
primul render → calculează suma;
dacă numbers nu se schimbă → React returnează suma memorată;
nu mai execută reduce().
Componenta se re-randează.
Fără useMemo, React execută iar filtrarea celor 100.000 produse.
cu useMemo
const filteredProducts = useMemo(() => {
return [Link](product =>
[Link] > 500 &&
[Link] === selectedCategory
);
}, [products, selectedCategory]);
Reguli importante
Include toate valorile folosite în funcția de calcul în lista de dependențe.
Nu folosi useMemo ca substitut pentru gestionarea stării (useState) sau a
efectelor (useEffect).
Optimizează doar atunci când există un motiv: de exemplu, ai observat re-
randări inutile sau calcule costisitoare.
XVI. Forms
În React, formularele sunt folosite pentru a colecta date de la utilizator (input-uri,
checkbox-uri, select-uri etc.). Ele se diferențiază de HTML simplu prin faptul că
React gestionează starea valorilor introduse, nu doar DOM-ul.
Explicație:
Explicație:
function FormularMultiRef() {
const emailRef = useRef();
const parolaRef = useRef();
Avantaje și dezavantaje:
Mai simplu pentru formulare mici.
Nu declanșează re-render la fiecare tastă (spre deosebire de useState).
Mai greu de validat în timp real, deoarece valorile nu sunt în React state.
function FormularFormData() {
const handleSubmit = (event) => {
[Link](); // prevenim reload-ul paginii
const form = [Link]; // elementul <form>
const formData = new FormData(form); // creăm FormData
const nume = [Link]('nume'); // preluăm valoarea câmpului "nume"
alert(`Ai introdus: ${nume}`);
};
return (
<form onSubmit={handleSubmit}>
<label>
Nume:
<input type="text" name="nume" />
</label>
<button type="submit">Trimite</button>
</form>
);
}
export default FormularFormData;
Explicație:
1. [Link] = formularul trimis.
2. new FormData(form) = obiect cu toate câmpurile din form.
3. [Link]('nume') = obții valoarea câmpului cu name="nume".
function FormularMultiFormData() {
const handleSubmit = (event) => {
[Link]();
const form = [Link];
const formData = new FormData(form);
const email = [Link]('email');
const parola = [Link]('parola');
[Link]('Email:', email);
[Link]('Parola:', parola);
};
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" placeholder="Email" />
<input type="password" name="parola" placeholder="Parolă" />
<button type="submit">Trimite</button>
</form>
);
}
export default FormularMultiFormData;
Avantaje:
Nu ai nevoie de useState sau useRef.
Poți prelua rapid date din formulare complexe, inclusiv fișiere (<input
type="file">).
Funcționează și pentru formularul complet fără să legi fiecare input de
state.
Dezavantaje:
Nu poți valida sau afișa instant valori, pentru că React nu „vede” valorile
până la submit.
Mai puțin flexibil pentru interacțiuni dinamice în timp real.
Resetare datelor unui formular:
folosind useState -> setForm({ nume: '', email: '' }); // resetare state → input-
urile devin goale
folosind useRef -> [Link] = '' resetează input-ul fără state.
folosind FormData -> FormData doar citește valorile, deci pentru resetare
trebuie să folosești metoda nativă [Link]():
FormData:
const nume = [Link]('nume').trim();
const email = [Link]('email').trim();
if (!nume || !/\S+@\S+\.\S+/.test(email)) {
alert('Formular invalid');
return;
}
XVII. useActionState
useActionState este un hook React care leagă o acțiune a utilizatorului
(ex. trimiterea unui formular) de o stare specifică. El actualizează starea automat
pe baza rezultatului acțiunii.
Sintaxa de bază:
const [state, formAction, isPending] = useActionState(actionFn, initialState);
actionFn – funcția care rulează la acțiunea utilizatorului, primind starea
curentă și datele formularului.
initialState – valoarea inițială a stării (obiect, string, număr etc.).
Ce returnează hook-ul:
Avantaje principale:
Reduce boilerplate-ul (nu mai trebuie state separate pentru loading, erori
sau valori trimise).
Simplifică actualizările asincrone fără useEffect.
Îmbunătățește performanța prin gestionarea eficientă a stării și evitarea re-
render-urilor inutile.
Exemplu:
"use client";
function FormApp() {
const [state, formAction, isPending] = useActionState(submitForm, {
success: null,
message: "",
});
return (
<div className="form-container">
<div className="form-card">
<form action={formAction}>
<input
className="form-input"
type="text"
name="name"
placeholder="Name"
/>
<input
className="form-input"
type="email"
name="email"
placeholder="Email"
/>
<button className="form-button" disabled={isPending}>
{isPending ? "Submitting..." : "Submit"}
</button>
{[Link] && (
<p
className={`form-message ${[Link] ? "success" : "error"}`}
>
{[Link]}
</p>
)}
</form>
</div>
</div>
);
}
XVIII. useForm
Ce face useForm?
useForm centralizează tot managementul formularului:
<input
type="password"
{...register("password", { minLength: 6 })}
placeholder="Parolă"
/>
{[Link] && <p>Parola este prea scurtă</p>}
<button type="submit">Login</button>
</form>
);
}
Validari
register("email", {
required: "Email obligatoriu",
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: "Email invalid",
},
});
watch
const { watch } = useForm();
const email = watch("email");
- randeaza componenta de fiecare data cand value se schimba;
reset
const { reset } = useForm();
reset();
- reseteaza valorile din form;
Controller
XIV. useFormStatus
Def! useFormStatus este un hook din React, introdus în special în contextul
formularelor din React Server Components sau cu framework-uri ca Remix sau
[Link] 13+, care te ajută să urmărești starea unui formular (ex. dacă este în
proces de trimitere). Practic, îți permite să faci UI mai reactiv la starea
formularului fără să gestionezi manual loading sau submitting.
Exemplu
'use client'; // necesar în [Link] 13+
import { useFormStatus } from 'react';
function MyForm() {
const { pending, submitting } = useFormStatus();
return (
<form action="/api/submit" method="post">
<input type="text" name="username" />
<button type="submit" disabled={submitting}>
{submitting ? 'Se trimite...' : 'Trimite'}
</button>
{pending && <p>Formularul se procesează...</p>}
</form>
);
}
Ce se întâmplă aici:
Când utilizatorul dă click pe „Trimite”, submitting devine true.
Butonul se dezactivează și textul se schimbă.
Dacă ai nevoie de un mesaj global „se procesează”, poți folosi pending.
XIX. Modal
Def! În React, „modalele” (modals) nu sunt o funcționalitate built-in a React, ci un
pattern de UI: o fereastră care apare peste conținutul principal și blochează sau
evidențiază o acțiune (confirmare, formular, detalii etc.).
Ce este un modal?
Un modal este o „fereastră superioară” (overlay) care:
Tipuri de modale
1. Modal simplu
Cum funcționează
ai un useState (true/false)
când true → componenta modal este randată
când false → nu există în DOM
Exemplu complet
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>
Deschide modal
</button>
{isOpen && (
<div className="modal">
<h2>Modal simplu</h2>
<button onClick={() => setIsOpen(false)}>
Închide
</button>
</div>
)}
</div>
);
}
Avantaje
foarte ușor de înțeles
rapid de implementat
zero dependențe
Dezavantaje
2. Modal cu portal
Modalul este randat în alt loc din DOM (de obicei în [Link]), nu în
ierarhia componentelor.
De ce?
evită probleme de z-index
nu este afectat de CSS-ul părintelui
se pune direct în [Link]
Cum funcționează
Avantaje
nu depinde de layout-ul paginii
rezolvă probleme de CSS
standard în aplicații mari
ușor de combinat cu animații
Dezavantaje
puțin mai complex
necesită înțelegerea DOM-ului
debugging ușor mai dificil
Functia stopPropagation
Ce face: Oprește propagarea unui eveniment (ex: click) în sus, către
elementele părinte.
Problema rezolvată: Previne declanșarea accidentală a funcțiilor de pe
părinți atunci când interacționezi cu un element copil (ex: închideri
accidentale de modale/drop-down-uri).
Sintaxă: onClick={(e) => [Link]()}
Exemplu:
const ModalContext = createContext();
function ModalProvider({ children }) {
const [modal, setModal] = useState(null);
return (
<[Link] value={{ modal, setModal }}>
{children}
{modal && <Modal>{modal}</Modal>}
</[Link]>
);
}
Avantaje
control centralizat
foarte scalabil
bun pentru aplicații mari
Dezavantaje
mai mult boilerplate
debugging mai complex
poate deveni „overengineering” în proiecte mici
Cum funcționează
Librăria îți oferă:
Exemplu:
<Modal show={show} onHide={onClose} centered size="lg">
<[Link] closeButton className="bg-primary text-white">
<[Link]>Date personale</[Link]>
</[Link]>
<[Link]>
<div className="border rounded p-3 bg-light">
<p><strong>Nume:</strong> {personalData?.inputName} </p>
<p><strong>Prenume:</strong> {personalData?.inputPrenume}</p>
<p><strong>Nr - Serie CI:</strong> {personalData?.inputSerie}
{personalData?.inputNumar}</p>
<p><strong>Email:</strong> {personalData?.inputEmail}</p>
</div>
</[Link]>
<[Link]>
<Button variant="outline-secondary" onClick={onClose}>
Închide
</Button>
</[Link]>
</Modal>
XX. useOptimistic
Def! useOptimistic este un hook din React folosit pentru actualizări optimiste
ale stării, adică atunci când vrei să actualizezi UI-ul imediat, înainte ca serverul să
confirme schimbarea. Este foarte util pentru UX rapid și fluid, mai ales în aplicații
cu interacțiuni sociale sau formulare.
Sintaxa de bază
const [state, update] = useOptimistic(initialState, reducer);
initialState – starea inițială.
reducer – funcție (state, action) => newState care definește cum se
schimbă starea optimist.
state – starea curentă (actualizată optimist)
update – funcție pe care o apelezi cu acțiunea dorită pentru a aplica
schimbarea optimist.
Exemplu:
'use client';
import { useOptimistic } from 'react';
function Counter() {
const [count, updateCount] = useOptimistic(0, (state, delta) => state + delta);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => updateCount(1)}>Crește</button>
<button onClick={() => updateCount(-1)}>Scade</button>
</div>
);
}
Ce se întâmplă:
Exemplu cu server:
function LikeButton({ postId }) {
const [likes, addLike] = useOptimistic(0, (state, action) => state + action);
async function handleLike() {
addLike(1); // update optimist
try {
await fetch(`/api/posts/${postId}/like`, { method: 'POST' });
} catch (err) {
addLike(-1); // rollback dacă serverul eșuează
}
}
return <button onClick={handleLike}>Like {likes}</button>;
}
XXI. Router
Def! Un router în React se ocupă cu:
Navigarea între pagini fără a reîncărca pagina.
Maparea URL-urilor la componente React.
Gestionarea istoricului (back/forward) și a stării navigației.
React Router v6+ are mai multe tipuri de routeri, dar cel mai folosit este
createBrowserRouter, care folosește URL-uri normale (nu hash-uri #).
Def! createBrowserRouter este o funcție din React Router v6.4+ care îți permite
să definești rutele aplicației tale folosind o structură de tip arbore și să folosești
un history bazat pe browser (adică URL-urile se schimbă normal în browser, fără
#). Este folosită pentru a crea un router modern cu suport pentru loader-e,
action-e și nested routes.
Definirea rutelor
Rutele se definesc ca un array de obiecte, fiecare obiect reprezentând o rută. Un
exemplu minimal:
import { createBrowserRouter } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
const router = createBrowserRouter([
{ path: "/", element: <Home /> },
{ path: "/about", element: <About /> },
]);
Prop Ce face
path URL-ul pentru rută. Poate fi /, /about, /users/:id
element Componenta React care se renderizează pentru ruta respectivă
children Array cu rute copil (nested routes)
loader Funcție async care preia date înainte de renderizare
action Funcție async pentru a procesa formulare sau evenimente
errorElement Componenta care se afișează dacă apare eroare în loader/action
Definirea rutelor nested
import { createBrowserRouter } from "react-router-dom";
import MainLayout from "./MainLayout";
import Home from "./pages/Home";
import About from "./pages/About";
const router = createBrowserRouter([
{
path: "/",
element: <MainLayout />,
children: [
{ path: "/", element: <Home /> },
{ path: "about", element: <About /> },
],
},
]);
import { Outlet, Link } from "react-router-dom";
function MainLayout() {
return (
<div>
<header>
<Link to="/">Home</Link> | <Link to="/about">About</Link>
</header>
<hr />
<Outlet /> {/* Aici se va afișa Home sau About */}
<footer>© 2026 MyApp</footer>
</div>
);
}
1. <Outlet /> nu trebuie să fie singur – poți avea header, footer, sidebar, etc.,
în același layout.
2. Dacă o rută nu are copii, <Outlet /> nu afișează nimic.
3. Poți combina <Outlet /> cu useLoaderData() pentru a încărca date specifice
copilului.
4. <Outlet context={...} /> – poți transmite date de la layout la rutele copil
folosind useOutletContext().
useParams
useParams este un hook din React Router care îți permite să preiei parametrii
dinamici din URL.
Un parametru din URL este partea care se schimbă între rute, definită cu : în
path-ul rutei.
Exemplu:
/users/1 → id = "1"
/users/42 → id = "42"
Utilizare
function User() {
const params = useParams(); // params este un obiect cu toți parametrii
[Link](params); // { id: "1" } dacă URL-ul este "/users/1"
Observații:
useLoaderData
Observații:
Utilizare useLoaderData
Outlet
Def! <Outlet /> este un placeholder în componenta ta React unde React Router
va reda componenta copil corespunzătoare rutei curente.
Fără <Outlet />, rutele copil nu ar avea unde să fie afișate.
Este folosit mai ales atunci când ai layout-uri comune pentru mai multe
rute.
Exemplu
import { createBrowserRouter } from "react-router-dom";
import MainLayout from "./MainLayout";
import Home from "./pages/Home";
import About from "./pages/About";
const router = createBrowserRouter([
{
path: "/",
element: <MainLayout />,
children: [
{ path: "/", element: <Home /> },
{ path: "about", element: <About /> },
],
},
]);
import { Outlet, Link } from "react-router-dom";
function MainLayout() {
return (
<div>
<header>
<Link to="/">Home</Link> | <Link to="/about">About</Link>
</header>
<hr />
<Outlet /> {/* Aici se va afișa Home sau About */}
<footer>© 2026 MyApp</footer>
</div>
);
}
export default MainLayout;
Observații importante
1. <Outlet /> nu trebuie să fie singur – poți avea header, footer, sidebar, etc.,
în același layout.
2. Dacă o rută nu are copii, <Outlet /> nu afișează nimic.
3. Poți combina <Outlet /> cu useLoaderData() pentru a încărca date specifice
copilului.
4. <Outlet context={...} /> – poți transmite date de la layout la rutele copil
folosind useOutletContext().
XII. useRouter
Hook-ul useRouter este un instrument fundamental în [Link] folosit pentru a
accesa programatic routerul și pentru a gestiona navigarea în interiorul aplicației.
Funcționarea sa diferă major în funcție de arhitectura pe care o folosești: App
Router (introdus în [Link] 13+, directorul /app) sau Pages Router (arhitectura
clasică, directorul /pages).
Exemplu:
import { useRouter } from 'next/router';
export default function DetaliiProdus() {
const router = useRouter();
const { id } = [Link]; // Extrage ID-ul din URL dynamic route
return (
<div>
<h1>Produsul cu ID-ul: {id}</h1>
<button onClick={() => [Link]('/')}>Înapoi acasă</button>
</div>
);
}
Când să folosești useRouter vs <Link>?
Folosește <Link> pentru orice navigare standard (meniuri, butoane simple, text
cu hiperlink). Este mai bun pentru SEO și face prefetch automat. [1]
Folosește useRouter doar când navigarea depinde de o acțiune logică (ex: după
ce un formular s-a trimis cu succes, după validarea unei plăți sau la selectarea
unei opțiuni dintr-un dropdown).
XXII. useSearchParams
Ce este searchParams?
Este un obiect care conține cheile și valorile din URL-ul paginii, după semnul
întrebării (?).
Permite implementarea de paginații, filtre, căutări și sortări dinamice.
Spre deosebire de parametri de rută (/produse/[id]), parametrii query (?id=123)
sunt opționali și flexibili.
De ce există search params?
În loc să păstrezi anumite informații doar în state-ul aplicației, le poți pune în
URL.
Avantaje:
utilizatorul poate da refresh fără să piardă filtrarea
poate trimite link-ul altcuiva
poate salva pagina la favorite
browser-ul ține istoricul modificărilor
În React Router
import { useSearchParams } from "react-router-dom";
const [searchParams, setSearchParams] = useSearchParams();
1. searchParams
Este un obiect de tip URLSearchParams (un API nativ al browserului) [].
Reprezintă starea curentă a parametrilor din URL.
Nu poți citi proprietățile direct ca dintr-un obiect normal ([Link] nu
va funcționa). Trebuie să folosești metode specifice [].
2. setSearchParams
Este o funcție care îți permite să modifici URL-ul [].
Funcționează exact ca funcția setStare dintr-un useState [].
Când o apelezi, ea schimbă parametrii din URL și declanșează o rerandare a
componentei cu noile valori.
Exemplu:
In NextJS
Cum se folosește în Server Components
Exemplu:
// app/produse/[Link]
// Definim tipul dacă folosești TypeScript (opțional)
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>;
export default async function Page({ searchParams }: { searchParams:
SearchParams }) {
// Așteptăm rezolvarea promise-ului pentru a citi parametrii
const params = await searchParams;
const queryCautare = [Link] || '';
const paginaCurenta = [Link] || '1';
return (
<div>
<h1>Rezultatele căutării pentru: {queryCautare}</h1>
<p>Te afli la pagina: {paginaCurenta}</p>
</div>
);
}
Dacă o componentă are directiva "use client", nu poți primi searchParams ca prop
direct. În schimb, [Link] îți pune la dispoziție un React Hook real numit
useSearchParams().
Acest hook returnează o instanță de tip URLSearchParams, ceea ce înseamnă că
citești valorile folosind metoda .get().
Exemplu:
"use client";
import { useSearchParams } from "next/navigation";
export default function FiltreProduse() {
const searchParams = useSearchParams();
// Citim parametrii din URL (?categorie=electronice)
const categorie = [Link]("categorie");
return (
<div>
<p>Categoria selectată: {categorie || "Toate"}</p>
</div>
);
}
return (
<input
type="text"
placeholder="Caută produse..."
defaultValue={[Link]("search")?.toString()}
onChange={(e) => handleSearch([Link])}
/>
);
}
Tanstack Query
Definitie! TanStack Query (fostă React Query) este o librărie pentru gestionarea
datelor asincrone în aplicații frontend — în special pentru React, dar există și
variante pentru Vue, Solid, Svelte etc.
Fara tanstack
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
fetch('/api/users')
.then(r => [Link]())
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [])
TanStack Query are câteva concepte fundamentale:
1. Query Client
2. Queries
3. Query Keys
4. Query Functions
5. Cache
6. Stale vs Fresh
7. Mutations
8. Invalidation
9. Refetching
10. Pagination / Infinite Queries
1. Query Client
Este “creierul” aplicației.
gestioneaza: cache-ul, queries, mutations, refetching, sincronizarea
setup: npm install @tanstack/react-query
configurare:
import {
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query"
2. Query
este o operatie de citirea a datelor;
exemple: get /users, get /posts, get /profile;
in general:
o queries = read;
o mutations = write;
3. useQuery
hook-ul principal;
exemplu:
import { useQuery } from "@tanstack/react-query"
function Users() {
const { data, isLoading, error} = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
});
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error</div>
return (
<ul>
{[Link](user => (
<li key={[Link]}>{[Link]}</li>
))}
</ul>
)
}
4. queryKey
identifica unic datele din cache;
ex: [“users”], [“users”, userId], [“posts”, {page: 1, filter: “react”}]
se foloseste pentru: cache, refetch, invalidation, deduplicare;
5. queryFn
functia care face request;
const fetchUsers = async () => {
const res = await fetch("/api/users")
if (![Link]) {throw new Error("Failed")}
return [Link]()
}
apoi
useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
})
6. Cache
tanstack cache-uieste automat datele;
avantaje:
o nu reface request-uri inutile,
o date instant,
o UX mai rapid,
o sincronizare între componente.
7. Stale vs Fresh
Cum functioneaza?
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 5000
})
8. Mutations
Exemplu:
const mutation = useMutation({
mutationFn: createUser
})
Trigger:
[Link]({
name: 'Alex'
})
State-urile mutation
[Link]
[Link]
[Link]
[Link]
[Link]
9. Invalidation
După o mutation, cache-ul poate deveni incorect.
invalidateQueries
const queryClient = useQueryClient()
[Link]({
queryKey: ['users']
})
Flux tipic:
const queryClient = useQueryClient()
invalidate vs refetch
invalidateQueries
marchează stale
eventual refetch
refetchQueries
face request imediat
În practică:
invalidate e preferat mai des
Iniginite Queries
const {data, fetchNextPage, hasNextPage, isFetchingNextPage} =
useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 1 }) =>
fetchPosts(pageParam),
getNextPageParam: (lastPage) => [Link]})
QueryClient
QueryClient este „motorul” care gestionează:
cache-ul pentru request-uri
refetch automat
invalidarea datelor
retry logic
sincronizarea între componente
stările loading/error/success
Practic, el ține în memorie toate query-urile aplicației.
Exemplu:
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient()
Exemplu:
import React from 'react'
import ReactDOM from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import App from './App'
const queryClient = new QueryClient()
[Link]([Link]('root')!).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
)
Fluxul este:
QueryClient
↓
QueryClientProvider
↓
toate componentele React
↓
useQuery / useMutation
Cand faci:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
hook-ul caută QueryClient din provider și:
verifică dacă datele există în cache
decide dacă trebuie refetch
salvează răspunsul
notifică toate componentele care folosesc acea query
Fără React Query:
const [data, setData] = useState()
const [loading, setLoading] = useState()
useEffect(() => {
fetch('/api/users')
.then(r => [Link]())
.then(setData)
}, [])
trebuie gestionate manual: loading, errors, cache, refetch, retry,
sincronizare;
Cu React Query:
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
totul devine automat;
createRoutesFromElements
createRoutesFromElements este o funcție care transformă JSX-ul rutei într-un
array de obiecte de rute.
În loc să scrii rutele ca array de obiecte ([{ path, element, children }]), le
poți scrie ca JSX, ceea ce poate fi mai intuitiv.
Este folosit împreună cu createBrowserRouter sau alte tipuri de routere.
Exemplu:
import { createBrowserRouter, createRoutesFromElements, Route } from "react-
router-dom";
import MainLayout from "./MainLayout";
import Home from "./pages/Home";
import About from "./pages/About";
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<MainLayout />}>
<Route index element={<Home />} /> {/* index = ruta implicită */}
<Route path="about" element={<About />} />
</Route>
)
);
<Route index element={...} /> = ruta implicită pentru layout-ul părinte (/).
<Route path="about" /> = ruta /about sub layout-ul /.
Link
<Link> este componenta din React Router care înlocuiește elementul HTML <a>
pentru navigarea internă între rute.
Navigarea se face fără reîncărcarea paginii (SPA – Single Page
Application).
Actualizează URL-ul și redă componenta corespunzătoare automat,
folosind router-ul.
Exemplu
Link absolut
<Link to="/about">About</Link>
Merge către /about din rădăcina site-ului.
Link relativ
<Link to="settings">Settings</Link>
Fără / la început, URL-ul este relativ la ruta curentă.
Fragment
În React, Fragment este un mecanism care îți permite să grupezi mai multe
elemente JSX fără să adaugi un element suplimentar în DOM (cum ar fi un <div>
inutil).
Fara Fragment:
function Exemplu() {
return (
<div>
<h1>Titlu</h1>
<p>Descriere</p>
</div>
);
}
Problema:
Cu Fragment:
import { Fragment } from 'react';
function Exemplu() {
return (
<Fragment>
<h1>Titlu</h1>
<p>Descriere</p>
</Fragment>
);
}
React grupează elementele logic, dar nu generează un element HTML în plus.
[Link](item => (
<Fragment key={[Link]}>
<h2>{[Link]}</h2>
<p>{[Link]}</p>
</Fragment>
))
getStaticProps
Idei generale:
rulează la build time;
generează pagini statice (HTML deja pregătit);
foarte rapid;
bun pentru conținut care nu se schimbă des;
Sintaxa de baza:
export async function getStaticProps() {
// fetch data from an API
return {
props: {
meetups: DUMMY_MEETUPS
},
revalidate: 1
};
}
Ce face functia?
rulează pe server;
ia datele;
creează HTML-ul paginii;
[Link] salvează pagina ca fișier static;
Flow-ul complet
Pasul 1 – Build: npm run build
Pasul 2 – Datele sunt obtinute: meetups: DUMMY_MEETUPS
const response = await fetch('[Link]
const data = await [Link]();
Pasul 3 – Se genereaza HTML static:
<h1>Lista Meetups</h1>
<ul>
...
</ul>
Ce inseamna props?
return {
props: {
meetups: DUMMY_MEETUPS
}
}
Ce face revalidate?
revalidate: 1 -> activeaza ISR = Incremental Static Regeneration
pagina este statica dar poate fi regenerata automat dupa minim 1
sec
User 1 -> primește pagina statică
după 1 secundă:
User 2 -> [Link] regenerează pagina în background
Cand se foloseste getStaticProps?
- blog
- produse
- landing pages
- documentație
- articole
- conținut public
getServerSideProps
Idei generale:
rulează la fiecare request
generează pagina pe server în timp real
mai flexibil
bun pentru date dinamice
Sintaxa de baza:
export async function getServerSideProps(context) {
const req = [Link];
const res = [Link];
// fetch data from an API
return {
props: {
meetups: DUMMY_MEETUPS
},
};
}
Ce face functia?
La fiecare request:
-> user intră pe pagină
-> [Link] rulează funcția
-> ia datele
-> generează HTML
-> trimite răspunsul
Ce este context?
context conține informații despre request.
const req = [Link]; -> este request-ul HTTP
const res = [Link]; -> este response-ul HTTP
Suspense
Definitie! Suspense în React este o componentă care te ajută să gestionezi stările
de „așteptare” (loading) în aplicații, mai ales când încarci date sau componente
care nu sunt disponibile imediat.
Cum functioneaza?
Unde se foloseste?
1. Lazy loading de componente
import { lazy, Suspense } from "react";
const Dashboard = lazy(() => import("./Dashboard"));
function App() {
return (
<Suspense fallback={<div>Se încarcă dashboard-ul...</div>}>
<Dashboard />
</Suspense>
);
}
Aici Dashboard se încarcă doar când e nevoie (code splitting).
Important
Nu funcționează „automat” cu orice async (fetch clasic în useEffect NU
declanșează Suspense).
Este gândit pentru:
o [Link]
o framework-uri care suportă Suspense data fetching
Este parte din arhitectura modernă React (Concurrent Features)
În RSC Framework Mode, React Router folosește un plugin Vite diferit față de
modul clasic, numit unstable_reactRouterRSC, care trebuie folosit împreună cu
pluginul experimental @vitejs/plugin-rsc. Ordinea contează: pluginul React
Router trebuie să fie înaintea celui RSC.
În RSC mode, loader și action pot returna date + elemente React, dar aceste
elemente sunt randate doar pe server.
export async function loader() {
return {
message: "Message from the server!",
element: <p>Element from the server!</p>,
};
}
export default function Route({ loaderData }) {
return (
<>
<h1>{[Link]}</h1>
{[Link]}
</>
);
}
"use client";
import { useState } from "react";
Ideea:
ServerComponent = randare pe server
loaderData vine direct din server
Outlet funcționează normal pentru nested routes
nu există componentă default
Ideea:
server și client au echivalente separate
nu se amestecă în același export
3. Componente client în interiorul unui ServerComponent
Dacă ai nevoie de hook-uri sau evenimente, trebuie să muți acea logică într-un
modul client ("use client").
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Ideea:
ServerComponent rămâne server-only
interactivitatea se izolează în componente client
Ideea:
import "server-only" blochează includerea în client build
validare se face la build-time prin @vitejs/plugin-rsc
5. Migrare de la convențiile vechi .server/.client
Se poate folosi vite-env-only pentru a preveni importurile greșite:
import { defineConfig } from "vite";
import { denyImports } from "vite-env-only";
import { unstable_reactRouterRSC as reactRouterRSC } from "@react-
router/dev/vite";
import rsc from "@vitejs/plugin-rsc";
În RSC Framework Mode, există suport pentru MDX routes, dar cu o limitare
importantă: componentele exportate din MDX trebuie să fie compatibile cu RSC
(adică fără Hooks sau alte feature-uri client-only). Dacă este nevoie de
interactivitate, logica trebuie mutată într-un modul "use client" separat.
Ideea:
MDX în RSC este server-first, nu „React complet interactiv”.
Ideea:
RSC entry controlează request handling server-side
poți intercepta request-uri înainte de routing
Ideea:
SSR entry controlează HTML output-ul final
poate fi extins, nu înlocuit complet
5. Client entry
Client entry poate rămâne default sau poate fi importat direct:
import "@react-router/dev/config/default-rsc-entries/[Link]";
Ideea:
client entry gestionează hydration-ul
de obicei nu necesită modificări
6. Customizare avansată
Pentru modificări profunde:
copiezi fișierele default din:
o [Link]
o [Link]
o [Link]
le modifici local
Acestea se găsesc în:
node_modules/@react-router/dev/dist/config/default-rsc-entries/
Obligatoriu păstrezi:
[Link] → export default { fetch }
[Link] → generateHTML
[Link] → hydration logic
7. Comenzi utile
Poți inspecta implementările implicite cu:
react-router reveal [Link]
react-router reveal [Link]
react-router reveal [Link]
Ideea generală
MDX este permis, dar strict server-safe
aplicația are 3 entry points clare: RSC, SSR, client
fiecare poate fi extins sau înlocuit controlat
RSC Mode elimină multe configurări vechi pentru a simplifica pipeline-ul și
a impune separarea server/client
RSC Data Mode este stratul de bază peste care este construit RSC Framework
Mode. Oferă mai multă flexibilitate, dar mai puține facilități out-of-the-box (fără
file-system routing, HMR sau hot data revalidation). Este util mai ales când vrei
integrare cu un bundler sau server personalizat.
1. Definirea rutelor (manual, fără file-based routing)
Rutele sunt definite explicit prin matchRSCServerRequest, folosind o structură de
config.
function Root() {
return <h1>Hello world</h1>;
}
matchRSCServerRequest({
routes: [{ path: "/", Component: Root }],
});
Ideea:
rutele nu mai vin din fișiere
sunt definite programatic
control complet asupra routing-ului
2. Recomandare: lazy route modules
Pentru performanță și organizare, se recomandă lazy() imports, care reiau
modelul de Route Modules din Framework Mode.
Ideea:
aceleași route module exports ca în Framework Mode:
o loader, action, meta, headers
o ErrorBoundary, HydrateFallback
o client/server annotations
diferența: totul e manual orchestrat
3. Server Components ca default route export
În Data Mode, fiecare rută exportă implicit un Server Component.
export default function Home() {
return (
<main>
<article>
<h1>Welcome to React Router RSC</h1>
<p>You won't find me running any JavaScript in the browser!</p>
</article>
</main>
);
}
Ideea:
totul rulează pe server by default
client JS nu este necesar
return (
<main>
<article>
<h1>Welcome to React Router RSC</h1>
<p>You won't find me running any JavaScript in the browser!</p>
<p>Hello, {user ? [Link] : "anonymous person"}!</p>
</article>
</main>
);
}
Ideea:
elimină nevoia de loader în multe cazuri
componenta devine direct strat de data-fetching + UI
Definire:
"use server";
export async function updateFavorite(formData: FormData) {
let movieId = [Link]("id");
let intent = [Link]("intent");
if (intent === "add") {
await addFavorite(Number(movieId));
} else {
await removeFavorite(Number(movieId));
}
}
Utilizarea intr-un form:
import { updateFavorite } from "./[Link]";
return (
<form action={updateFavorite}>
<input type="hidden" name="id" value={movieId} />
<input type="hidden" name="intent" value={isFav ? "remove" : "add"} />
<AddToFavoritesButton isFav={isFav} />
</form>
);
}
Ideea:
funcțiile rulează pe server automat
pot fi apelate direct din forms
React Router revalidează automat UI după execuție
7. Auto revalidare
După apelul unui Server Function:
React Router face automat:
o re-fetch / re-render server UI
o update UI fără cache invalidation manual
Ideea:
nu gestionezi manual cache-ul
sistemul menține consistența server-client automat
În RSC, rutele sunt definite pe server, dar pot expune logică de tip client prin
mecanisme speciale (clientLoader, clientAction, shouldRevalidate) folosind "use
client" și module separate. Acest lucru permite un mix controlat între server
routing și comportament client-side.
Ideea:
clientLoader → fetch pe client
clientAction → mutații pe client
shouldRevalidate → control re-fetch/re-render
totul rulează în context "use client"
export {
clientAction,
clientLoader,
shouldRevalidate,
} from "./client";
Ideea:
route-ul server devine „wrapper”
logica client este importată separat
păstrează separarea server/client, dar le leagă logic
export {
clientAction,
clientLoader,
shouldRevalidate,
} from "./client";
export default function Root() {
// necesar ca bundlerul să păstreze un server boundary
return <ClientRoot />;
}
Ideea:
ruta rămâne compatibilă cu RSC pipeline
UI real este complet client-rendered
server component există doar ca „boundary”
6. Ideea generală
rutele sunt server-defined, dar pot expune logică client
client logic este izolat în "use client" modules
route modules doar re-exportă comportamente client
aplicația are 3 entry points clare:
o RSC server
o SSR server
o browser hydration
totul este orchestrat de bundler, nu de framework magic
RENDER
────────────────────────
✔ Rulează componentele
✔ Calculează schimbările
✔ Lucrează în memorie
✖ Nu modifică DOM-ul
COMMIT
────────────────────────
✔ Aplică schimbările
✔ Modifică DOM-ul real
✔ Pregătește afișarea
Exemplu
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
Click
│
▼
setCount()
│
▼
Render Triggered
│
▼
React rulează Counter()
│
▼
Observă că valoarea s-a schimbat
│
▼
Commit Phase
│
▼
DOM actualizat
│
▼
Browser Paint
│
▼
Utilizatorul vede noua valoare
Render Triggered
Procesul de renderizare React este declanșat în două situații:
1. La încărcarea inițială a aplicației (Initial Render).
2. Când se modifică state-ul sau props-urile unei componente.
Important: React nu execută render-ul instantaneu. Actualizările sunt
planificate (scheduled) și grupate (batching) pentru a evita re-renderizările inutile
și pentru a optimiza performanța.
Render Phase
În această etapă React calculează cum ar trebui să arate interfața după
modificări.
Virtual DOM
Virtual DOM este o copie ușoară a DOM-ului real păstrată în memorie.
Când state-ul se schimbă:
Diagrama
Real DOM
▲
│ sincronizare
│
Virtual DOM (vechi)
│
▼
State schimbat
│
▼
Virtual DOM (nou)
Re-renderizarea componentelor
Când o componentă părinte se re-renderizează:
React execută din nou și componentele copil.
Nu înseamnă că DOM-ul lor va fi actualizat.
React verifică ulterior dacă există modificări reale.
Fiber Reconciler
Ce stochează un Fiber?
State
Props
Hooks
Side Effects
Task-uri în așteptare
Structura Fiber
App Fiber
│
├── Header Fiber
│
├── Sidebar Fiber
│
└── Content Fiber
│
├── Card Fiber
│
└── Button Fiber
Diffing Algorithm
Diffing este algoritmul care compară două arbori Virtual DOM și găsește
modificările minime necesare.
Nou
A
B
D
Păstrează A
Păstrează B
Înlocuiește C cu D
Fluxul complet React in spatele scenei
State Update
│
▼
Render Triggered
│
▼
Nou Virtual DOM
│
▼
Fiber Reconciler
│
▼
Diffing Algorithm
│
▼
Lista modificărilor
│
▼
Commit Phase
│
▼
Actualizare Real DOM
│
▼
Browser Paint
│
▼
UI actualizat
Commit Phase
Diagrama
React
│
│
▼
Virtual DOM
│
▼
Renderer
│
┌───────┴────────┐
│ │
▼ ▼
React DOM React Native
│ │
▼ ▼
Browser DOM Native UI
Exemplu:
Actualizări necesare:
✓ Modifică textul
✓ Adaugă un buton
✓ Șterge un element
Diagrama
sincronă;
executată dintr-o singură operație;
nu poate fi întreruptă.
Motivul:
React trebuie să garanteze că utilizatorul nu vede o interfață incomplet
actualizată.
Diagrama
Render Phase
──────────────
Poate fi:
✓ întreruptă
✓ reluată
✓ prioritizată
Commit Phase
──────────────
✓ executare completă
✗ nu poate fi întreruptă
✗ nu poate fi împărțită
După Commit:
Înainte:
Current Tree
│
▼
UI curentă
După Commit:
Browser Paint
Diagramă
Commit Phase
│
▼
DOM actualizat
│
▼
Browser Layout
│
▼
Browser Paint
│
▼
Utilizatorul vede schimbarea
[Link] = {
count: 0
};
}
render() {
return (
<div>
<p>Count: {[Link]}</p>
</div>
);
}
}
Modificarea state-ului
[Link] = 1; // Greșit
[Link]({
count: 1
});
Lifecycle Methods
Un motiv major pentru existența class components era accesul la ciclul de viață al
componentei.
Problema cu this
În class components trebuie să fii atent la contextul lui this.
Class component:
class Counter extends [Link] {
state = { count: 0 };
render() {
return (
<button
onClick={() =>
[Link]({
count: [Link] + 1
})
}
>
{[Link]}
</button>
);
}
}
Function component:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
Exemplu:
<select>
<option>România</option>
<option>Franța</option>
</select>
Cu Compound Components:
<Accordion>
<[Link]>
<[Link]>FAQ 1</[Link]>
<[Link]>
Răspuns 1
</[Link]>
</[Link]>
<[Link]>
<[Link]>FAQ 2</[Link]>
<[Link]>
Răspuns 2
</[Link]>
</[Link]>
</Accordion>
Cum funcționează
De obicei se bazează pe:
1. Un component părinte
2. Componente copil
3. React Context pentru partajarea stării
Avantaje:
API intuitiv
Flexibilitate
Evită "prop drilling"
Dezavantaje:
Necesită Context
Mai greu de urmărit pentru începători
Dependință de structură
return (
<[Link]
value={{
activeTab,
setActiveTab,
}}
>
{children}
</[Link]>
);
}
function TabList({ children }) {
return <div>{children}</div>;
}
return (
<button
onClick={() => setActiveTab(id)}
style={{
fontWeight:
activeTab === id ? "bold" : "normal",
}}
>
{children}
</button>
);
}
return <div>{children}</div>;
}
<Tabs>
<[Link]>
<[Link] id="1">Profil</[Link]>
<[Link] id="2">Setări</[Link]>
</[Link]>
<[Link] id="1">
Conținut profil
</[Link]>
<[Link] id="2">
Conținut setări
</[Link]>
</Tabs>
Render Props
Definitie! Render Props este un pattern React prin care o componentă nu decide
singură ce să afișeze, ci oferă date sau comportament unei funcții primite prin
props, iar acea funcție returnează JSX-ul care trebuie randat.
În loc să primești doar date prin props, primești acces direct la starea internă a
componentei.
Ideea de bază
O componentă expune ceva:
<DataProvider
render={(data) => (
<div>{[Link]}</div>
)}
/>
Exemplu
Să construim o componentă care urmărește poziția mouse-ului.
Fara Render Props:
function MouseTracker() {
const [position, setPosition] = useState({
x: 0,
y: 0,
});
useEffect(() => {
const handleMove = (e) => {
setPosition({
x: [Link],
y: [Link],
});
};
[Link]("mousemove", handleMove);
return () =>
[Link](
"mousemove",
handleMove
);
}, []);
return (
<div>
X: {position.x}
Y: {position.y}
</div>
);
}
Cu Render Props:
function MouseTracker({ render }) {
const [position, setPosition] = useState({
x: 0,
y: 0,
});
useEffect(() => {
const handleMove = (e) => {
setPosition({
x: [Link],
y: [Link],
});
};
[Link]("mousemove", handleMove);
return () =>
[Link](
"mousemove",
handleMove
);
}, []);
return render(position);
}
<MouseTracker
render={({ x, y }) => (
<h1>
Mouse: {x}, {y}
</h1>
)}
/>
Poți schimba complet UI-ul fără să modifici componenta:
<MouseTracker
render={({ x, y }) => (
<div
style={{
position: "absolute",
left: x,
top: y,
}}
>
</div>
)}
/>
Varianta cu children
function MouseTracker({ children }) {
const [position, setPosition] = useState({
x: 0,
y: 0,
});
return children(position);
}
<MouseTracker>
{({ x, y }) => (
<p>
{x}, {y}
</p>
)}
</MouseTracker>
function DataFetcher({
url,
children,
}) {
const [data, setData] = useState(null);
const [loading, setLoading] =
useState(true);
useEffect(() => {
fetch(url).then((res) => [Link]()).then((result) => {
setData(result);
setLoading(false);
});
}, [url]);
return children({
data,
loading,
});
}
<DataFetcher url="/api/users">
{({ data, loading }) => {
if (loading) {
return <p>Loading...</p>;
}
return (
<ul>
{[Link](user => (
<li key={[Link]}>
{[Link]}
</li>
))}
</ul>
);
}}
</DataFetcher>
Înainte de Hooks, reutilizarea logicii era dificilă. Existau două soluții principale:
Higher-Order Components (HOC)
o withMouseTracker(MyComponent)
Render Props
<MouseTracker>
{(mouse) => (
<MyComponent mouse={mouse} />
)}
</MouseTracker>
Debouncing
Definitie! Debouncing este o tehnică prin care amâni executarea unei funcții
până când utilizatorul încetează să mai declanșeze evenimentul pentru o anumită
perioadă de timp.
Problema
Să presupunem că ai un câmp de căutare:
<input
onChange={(e) => {
search([Link]);
}}
/>
Daca utilizatorul scrie react, se vor face 5 apeluri: r, re, rea, reac, react.
Soluția: Debouncing
Cum funcționează
Ideea este:
Exemplu in React
import { useState } from "react";
function Search() {
const [query, setQuery] = useState("");
return (
<input
value={query}
onChange={(e) =>
setQuery([Link])
}
/>
);
}
Varianta cu useEffect:
const [query, setQuery] = useState("");
const [debouncedQuery, setDebouncedQuery] =
useState("");
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedQuery(query);
}, 500);
return () => {
clearTimeout(timer);
};
}, [query]);
Aici:
fiecare modificare a lui query creează un timer nou
cleanup-ul anulează timerul precedent
doar ultima valoare supraviețuiește
useEffect(() => {
if (!debouncedQuery) return;
fetchUsers(debouncedQuery);
}, [debouncedQuery]);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}
XXVII. Testing
Definitie! Testing în React înseamnă verificarea faptului că aplicația ta
funcționează corect, atât la nivel de componente individuale, cât și la nivelul
întregii aplicații. În practică, testing-ul în React se împarte în mai multe categorii
și folosește diferite unelte.
1. De ce facem testing?
Scopurile principale sunt:
Detectarea bug-urilor înainte de producție.
Prevenirea regresiilor (ceva care funcționa înainte și se strică după o
modificare).
Documentarea comportamentului aplicației.
Creșterea încrederii când refactorizezi codul.
Componenta:
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<p>{count}</p>
<button
onClick={() =>
setCount(count + 1)
}
>
Increment
</button>
</>
);
}
Test:
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
Hook:
function useCounter() {
const [count, setCount] = useState(0);
return {
count,
increment: () =>
setCount(c => c + 1),
};
}
Test:
import { renderHook, act } from "@testing-library/react";
test("increments hook value", () => {
const { result } =
renderHook(() => useCounter());
act(() => {
[Link]();
});
expect(
[Link]
).toBe(1);
});
5. Mocking
Definitie! Mocking -> inlocuirea unor dependente reale cu versiuni controlate;
useEffect(() => {
fetch("/users")
.then(res => [Link]())
.then(setUsers);
}, []);
return (
<>
{[Link](user => (
<p key={[Link]}>
{[Link]}
</p>
))}
</>
);
}
render(<Users />);
expect(
await [Link]("John")
).toBeInTheDocument();
- evita: expect([Link]).toBe(1);
o Utilizatorul nu vede state-ul.
- prefera: expect([Link]("1")).toBeInTheDocument();
o Utilizatorul vede textul afișat.
2. Gandeste ca un utilizator
Întreabă-te:
Ce vede utilizatorul?
Ce poate apăsa?
Ce poate introduce?
Ce mesaj apare?
4. userEvent vs fireEvent
- userEvent -> imita comportamentul browser-ului mult mai realist decat
alte metode;
- fireEvent -> trimite evenimentul instantaneu catre element;
XXIII. Redux
Definitie! Redux este o librărie de gestionare a stării (state management)
pentru aplicații JavaScript, utilizată cel mai des împreună cu React pentru a
stoca și manageria datele globale ale unei aplicații într-un mod predictibil.
În aplicațiile mari de React, transmiterea datelor de la un component părinte la
un component copil situat la câteva niveluri distanță devine dificilă (fenomen
numit prop drilling). Redux rezolvă această problemă prin mutarea întregii stări
globale într-un singur loc centralizat numit Store, de unde orice component din
aplicație poate citi sau actualiza datele direct.
1. Action
Este un obiect JavaScript simplu care descrie ce vrei să faci. Are obligatoriu un
câmp type (o etichetă text) și, opțional, un payload (datele trimise).
// O acțiune pentru adăugarea unui produs în coș
const addToCartAction = {
type: 'cart/addItem',
payload: { id: 101, name: 'Căști Wireless', price: 299 }
};
2. Reducer-ul (Procesatorul)
Este o funcție care primește starea curentă și acțiunea trimisă, apoi calculează
noua stare.
Regulă de aur: Reducer-ul nu modifică (mutate) starea veche, ci creează o copie
complet nouă a acesteia.
3. Store-ul (Depozitul)
Este obiectul central care leagă acțiunile și reducerii. Acesta deține starea
aplicației și oferă câteva metode cheie:
dispatch(action) -> Trimite o acțiune către reducer (echivalentul trimiterii
formularului la casier).
getState() -> Returnează starea curentă.
subscribe(listener) -> Permite aplicației să asculte când se modifică starea
pentru a reîncărca interfața grafică.
În trecut, Redux era criticat pentru că rula foarte mult cod repetitiv (boilerplate).
Trebuia să configurezi manual Store-ul, să scrii fișiere separate pentru acțiuni,
constante și reduceri.
Astăzi, echipa Redux a creat Redux Toolkit (RTK). Acesta simplifică totul prin
conceptul de Slice (o felie din starea aplicației). Un Slice generează automat
acțiunile și reducerii în același loc și folosește sub capotă o librărie numită Immer,
care îți permite să scrii cod care pare că modifică direct starea, dar o face în
siguranță.
import { createSlice } from '@reduxjs/toolkit';
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [], total: 0 },
reducers: {
addItem: (state, action) => {
// Cu RTK poți folosi direct .push(), se ocupă Immer de imutabilitate!
[Link]([Link]);
[Link] += [Link];
},
clear: (state) => {
[Link] = [];
[Link] = 0;
}
}
});
export const { addItem, clear } = [Link];
export default [Link];
Exemplu complet: Cos de cumparaturi
const initialState = {
items: [], // Aici vom salva produsele adăugate
};
[Link]([Link]('root')).render(
<[Link]>
{/* Oferim acces întregii aplicații la Store-ul Redux */}
<Provider store={store}>
<App />
</Provider>
</[Link]>
);
return (
<div style={{ border: '1px solid gray', padding: '15px', margin: '10px' }}>
<h2>Produse Disponibile</h2>
{[Link](product => (
<div key={[Link]} style={{ marginBottom: '10px' }}>
<span>{[Link]} - {[Link]} RON </span>
{/* Tritem produsul ca payload în acțiunea addItem */}
<button onClick={() => dispatch(addItem(product))}>
Adaugă în coș
</button>
</div>
))}
</div>
);
}
return (
<div style={{ border: '1px solid green', padding: '15px', margin: '10px' }}>
<h2>Coșul Tău ({[Link]} produse)</h2>
{[Link] === 0 ? (
<p>Coșul este gol.</p>
):(
<ul>
{[Link]((item, index) => (
<li key={index} style={{ marginBottom: '5px' }}>
{[Link]} - {[Link]} RON{' '}
{/* Trimitem doar ID-ul produsului pentru ștergere */}
<button onClick={() => dispatch(removeItem([Link]))}>
Elimină
</button>
</li>
))}
</ul>
)}
<h3>Total: {totalPrice} RON</h3>
</div>
);
}
XXVIII. useDispatch
return (
<div>
{/* 2. Trimitem acțiunea când utilizatorul dă click */}
<button onClick={() => dispatch(increment())}>
Mărește valoarea
</button>
</div>
);
}
3. Conceptele Fundamentale
Pentru a înțelege și stăpâni useDispatch, trebuie să fii familiarizat cu câteva
concepte de bază din ecosistemul Redux:
A. Acțiunile (Actions)
O acțiune este un simplu obiect JavaScript care are o proprietate type
(obligatorie) și, opțional, date suplimentare (de obicei numite payload).
// O acțiune simplă
{
type: 'counter/increment'
}
// O acțiune cu payload
{
type: 'counter/incrementByAmount',
payload: 5
}
Notă: Când folosești Redux Toolkit, nu trebuie să creezi aceste obiecte manual; ele
sunt generate automat de către "slice-uri".
B. Reducerii (Reducers)
Reducerii sunt funcții pure care preiau starea curentă și o acțiune, iar pe baza
tipului de acțiune (type), calculează și returnează noua stare.
dispatch trimite acțiunea direct către reduceri.
C. Store-ul (The Store)
Este depozitul central care ține toată starea aplicației. Când faci
dispatch(acțiune), store-ul primește acțiunea, rulează reducerul corespunzător și
actualizează starea.
D. Funcții asincrone (Thunks)
Dacă ai nevoie să faci o operațiune asincronă (cum ar fi un apel la un API/server)
înainte de a face dispatch (de exemplu, afișezi un status de "loading", aștepți
datele, apoi faci dispatch cu succes), folosești Thunks. În Redux Toolkit, acestea
sunt create cu createAsyncThunk și trimise exact la fel:
dispatch(fetchUserData()).
4. Bune practici (Best Practices)
Folosește acțiuni pregătite (Action Creators): În loc să scrii manual dispatch({
type: 'increment' }), folosește funcțiile exportate de slice-urile tale:
dispatch(increment()). Acest lucru previne greșelile de scriere și centralizează
logica.
Tipizarea în TypeScript (dacă folosești TS): În aplicațiile mari, este o practică
standard să creezi un hook personalizat de dispatch pentru a asigura tipizarea
corectă (ex: incluzând tipurile pentru Thunk-uri)
// În componentă:
const dispatch = useAppDispatch();
XXIX. „Thunk”
1. Ce înseamnă, de fapt, un „Thunk”?
În programare, termenul general de „thunk” reprezintă o funcție care amână o
evaluare sau o execuție până mai târziu.
În loc să execute o bucată de cod imediat, o împachetezi într-o funcție pe care o
poți apela exact atunci când ai nevoie.
try {
const response = await fetch(`[Link]
const data = await [Link]();
// Dacă totul e ok, trimitem datele către reducer
dispatch({ type: 'user/fetchSuccess', payload: data });
} catch (error) {
// Dacă apare o eroare, trimitem eroarea
dispatch({ type: 'user/fetchFailed', payload: [Link] });
}
};
}
Definiție
Normalizarea datelor reprezintă procesul prin care informațiile provenite din
surse externe (API, fișiere JSON, baze de date sau alte componente) sunt
transformate într-o structură internă unitară, predictibilă și compatibilă cu
cerințele aplicației.
Într-o aplicație React, normalizarea are rolul de a asigura faptul că toate
componentele primesc date într-un format consistent, eliminând necesitatea
verificărilor repetate pentru multiple structuri de date posibile.
Exemplu:
function normalizeOption(value) {
if (!value) return null;
return {
key: [Link],
label: [Link] ?? [Link]
};
}
Această funcție creează un contract intern de date:
{
key: string,
label: string
}
Toate componentele care folosesc aceste date pot presupune existența aceleiași
structuri.
Normalizarea în fluxul unei aplicații React
Într-o aplicație React, normalizarea este de obicei realizată la intrarea datelor în
aplicație:
Sursă externă
(JSON / API / Database)
|
↓
Normalizare date
|
↓
State React
|
↓
Componente UI
|
↓
Interacțiunea utilizatorului
|
↓
Pregătire date pentru salvare
|
↓
API / Database
Prin această abordare, componentele de interfață nu trebuie să cunoască
formatul inițial al datelor.
Exemplu practic
Un câmp de tip radio poate veni dintr-un fișier JSON astfel:
{
warranties: {
key: "radio-2",
value: "Garanție standard"
}
}
Componenta React poate avea nevoie însă de:
{
warranties: {
key: "radio-2",
label: "Garanție standard"
}
}
Funcția de normalizare realizează conversia:
normalizeOption(warranties)
Rezultatul este o structură compatibilă cu componenta:
<input
type="radio"
checked={[Link] === [Link]}
/>
Beneficiile normalizării
1. Consistență
Toate componentele lucrează cu același format de date.
2. Reducerea complexității
Logica de conversie este centralizată într-un singur loc, nu repetată în fiecare
componentă.
3. Mentenabilitate
Modificarea structurii datelor externe necesită schimbări doar în funcțiile de
normalizare.
4. Reducerea erorilor
Se evită situațiile în care o componentă așteaptă un obiect, dar primește un
string sau o altă structură.
5. Separarea responsabilităților
Componentele React se concentrează pe afișare și interacțiune, iar funcțiile de
normalizare se ocupă de transformarea datelor.