ALGO25 / BLOG

Interviu React mid: efecte și cereri asincrone

Pregătește interviul React mid: useEffect, cleanup, dependențe și răspunsuri întârziate, cu un exemplu interactiv, erori simulate și autoevaluare.

Un utilizator deschide profilul A, apoi profilul B. Cererea pentru B se termină prima, dar răspunsul vechi pentru A ajunge mai târziu. Dacă afișezi necondiționat ultimul răspuns primit, pagina poate arăta datele altui profil. La nivel mid trebuie să poți explica și verifica această problemă de ordine.

Ce pregătești la acest nivel

Pornește de la state și actualizări. Ai nevoie și de promisiuni JavaScript. Continuă cu efecte, dependențe, cleanup, modelarea stărilor de încărcare și teste pentru erori. Exemplul folosește React 19 și o funcție asincronă simulată, fără rețea și fără biblioteci suplimentare.

Scopul este înțelegerea sincronizării. Într-o aplicație reală, mecanismul de date al frameworkului sau o bibliotecă de cache poate rezolva mai bine încărcarea, deduplicarea și reutilizarea rezultatelor. Nu prezenta acest exemplu ca un client complet de date pentru producție.

Exemplu: răspunsurile vechi nu schimbă profilul nou

Funcția loadProfile întârzie intenționat răspunsul pentru A. Este definită în afara componentei, deci referința ei este stabilă. Numele profilului este ales numai dintr-o listă locală, controlată.

import { useEffect, useState } from 'react';

function loadProfile(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id === 'error') reject(new Error('Profil indisponibil'));
      else resolve({ name: id === 'a' ? 'Ana' : 'Bogdan' });
    }, id === 'a' ? 300 : 30);
  });
}

export default function Profiles() {
  const [id, setId] = useState('a');
  const [result, setResult] = useState(null);
  const current = result?.id === id ? result : null;

  useEffect(() => {
    let ignore = false;
    loadProfile(id).then(
      profile => {
        if (!ignore) setResult({ id, status: 'success', profile });
      },
      () => {
        if (!ignore) setResult({ id, status: 'error' });
      }
    );
    return () => { ignore = true; };
  }, [id]);

  function choose(nextId) {
    if (nextId !== id) {
      setResult(null);
      setId(nextId);
    }
  }

  return (
    <section aria-label="Profiluri de test">
      <button type="button" onClick={() => choose('a')}>Profil A</button>
      <button type="button" onClick={() => choose('b')}>Profil B</button>
      <button type="button" onClick={() => choose('error')}>Simulează eroare</button>
      <p role="status">
        {!current ? 'Se încarcă…' : current.status === 'error'
          ? 'Nu am putut încărca profilul.' : current.profile.name}
      </p>
    </section>
  );
}

Montează componenta într-un proiect React cu JSX. La început se încarcă A. Apasă B înainte de cele 300 de milisecunde. Vezi Bogdan și după sosirea răspunsului pentru A. Butonul de eroare trebuie să înlocuiască încărcarea cu un mesaj clar. Exemplul nu folosește un endpoint real și nu cere acces la un cont.

De ce funcționează cleanup

Fiecare execuție a efectului are propria variabilă ignore. La schimbarea dependenței, cleanupul execuției vechi o setează la true. Promisiunea veche poate continua, dar nu mai are voie să actualizeze rezultatul. Noua execuție are o altă variabilă, inițial false.

Aceasta este o protecție împotriva rezultatului depășit, nu anularea operației. Pentru un fetch real poți folosi și AbortController, ținând cont de erorile de anulare. Dacă o operație a modificat deja serverul, anularea așteptării în browser nu inversează acea modificare. Mecanismul de ignorare este descris în sincronizarea prin efecte.

Păstrăm ID-ul împreună cu rezultatul și afișăm numai rezultatul corespunzător selecției. La schimbarea selecției eliminăm rezultatul anterior, inclusiv când revenim rapid de la A la B și înapoi la A. Astfel politica exemplului este explicită: arătăm încărcare, nu un cache de profiluri.

Dependențe și StrictMode

Efectul citește id, deci îl include în dependențe. Omiterea lui ar fixa efectul pe selecția inițială. Dacă loadProfile ar veni din props, ar participa și ea la analiza dependențelor. Nu ascunde avertismentele ca să obții numărul dorit de apeluri.

În dezvoltare, StrictMode poate executa un ciclu suplimentar de setup și cleanup pentru a detecta probleme. Codul corect trebuie să suporte această secvență. Nu rezolva o conexiune neînchisă dezactivând verificarea. Vezi StrictMode.

Exersează

  1. Elimină temporar protecția ignore. Repetă selecția rapidă A, B și explică de ce starea internă poate primi răspunsul vechi chiar dacă verificarea ID-ului previne afișarea profilului greșit.
  2. Testează A, B, A rapid. Rezultatul final trebuie să fie Ana, iar un răspuns vechi nu trebuie să încheie încărcarea noii selecții.
  3. Desenează patru stări: încărcare, succes, eroare și componentă demontată. Spune ce răspunsuri mai au dreptul să actualizeze fiecare stare.

Autoevaluare și pasul următor

Poți continua dacă explici diferența dintre ignorare și anulare, alegi corect dependențele și testezi răspunsuri în ordine inversă. Urmează React senior: performanță măsurată.

Întrebări frecvente

Un array gol de dependențe rezolvă cererile repetate?

Nu dacă efectul depinde de selecție. Ar împiedica sincronizarea cu noul ID și ar schimba comportamentul cerut.

Cleanup oprește promisiunea din exemplu?

Nu. Promisiunea continuă, însă rezultatul ei poate fi ignorat. Este important să deosebești cele două garanții.

De ce nu afișăm eroarea brută?

Utilizatorul are nevoie de un mesaj clar. Într-un sistem real, detaliile tehnice se înregistrează controlat pentru diagnosticare.

Pot folosi exemplul pentru salvarea unei comenzi?

Nu direct. O modificare de business cere validare, autorizare și o strategie pentru repetări pe server, pe lângă starea interfeței.

Este obligatoriu să încarc datele în useEffect?

Nu. Alege mecanismul de date potrivit aplicației. Exemplul izolează intenționat o problemă de sincronizare pentru a o putea explica.

Surse și versiuni

React 19, useEffect. Documentație consultată la 12 octombrie 2026.