Quan un equip de producte dissenya un flux d'aprovació, tot sembla senzill: l'usuari fa clic a 'Aprovar', el sistema registra la decisió i s'envia un correu electrònic de confirmació. No obstant això, quan s'implementa a React, la realitat sol ser molt diferent. Els efectes secundaris no controlats, les rehidratacions d'estat i els re-renders dupliquen les notificacions, generant queixes de clients i logs confusos. Aquest problema, més comú del que sembla, té una solució que va més enllà de corregir un useEffect: requereix repensar com separem la intenció de l'usuari de la lògica d'efectes.
A Q2BSTUDIO, empresa especialitzada en aplicacions a mida, hem vist desenes de projectes on el mateix patró causa estralls. La temptació d'usar un efecte per reaccionar a un canvi d'estat —per exemple, quan la propietat status passa a approved— porta a que, després d'un refetch o una restauració de pestanya, el correu es dispari dues vegades. No és un bug improbable; és inevitable si el frontend decideix quan enviar el correu.
L'alternativa robusta consisteix a tractar l'acció d'aprovar com un event explícit, no com una transició d'estat local. El botó dispara una petició amb una clau d'idempotència, el backend registra l'event en una taula d'outbox, i un worker desacoblat envia el correu només si aquest event és nou. React es limita a gestionar la interacció i actualitzar la UI des de l'estat fresc del servidor. El resultat: zero correus duplicats i una traçabilitat perfecta als logs. Aquest enfocament encaixa de forma natural amb arquitectures cloud modernes, com les que implementem amb serveis cloud AWS/Azure i intel·ligència artificial integrada, on la fiabilitat de les notificacions és crítica.
La pregunta que sorgeix és: per què seguim veient useEffect com l'orquestrador de fluxos de treball? Perquè sembla inofensiu i ràpid. Però no ho és quan el component es desmunta, es torna a muntar o rep nous props. La funció d'efecte s'executa cada cop que la seva dependència canvia, i si el servidor respon amb un estat actualitzat després de la mutació, el canvi es repeteix, llançant una altra crida. Molts equips intenten pedregar-ho amb flags o refs, però això afegeix complexitat i segueix sent fràgil. A Q2BSTUDIO recomanem un canvi de mentalitat: que el frontend només expressi intencions, i que el backend sigui l'únic propietari de les decisions de negoci, inclòs l'enviament de correus.
Implementar aquesta separació no requereix una arquitectura complexa. N'hi ha prou amb afegir una clau d'idempotència única a cada sol·licitud d'aprovació, generada al client (per exemple, amb crypto.randomUUID()). El servidor comprova si aquesta clau ja ha estat processada; si ho ha estat, respon amb un duplicat sense executar la lògica de correu. La transacció de base de dades escriu en una taula d'outbox l'event de correu pendent. Un worker independent —o un sistema de cues com SQS a AWS— consumeix aquesta taula i envia el correu. Així, encara que l'usuari faci clic diverses vegades o la pàgina es recarregui, només es produirà una notificació. Aquest patró és també la base per construir agents d'IA que prenguin decisions basades en events fiables, sense duplicacions.
Un altre aspecte clau és l'experiència d'usuari. En desacoblar la decisió d'enviament de la UI, podem optimitzar el feedback immediat: deshabilitar el botó durant la petició, mostrar un toast d'èxit o fins i tot un indicador de progrés, sense por que això generi efectes secundaris. Si el backend respon amb duplicate: true, la UI pot mostrar un missatge amigable com 'Ja has aprovat aquesta sol·licitud'. Aquesta separació també facilita la integració amb eines de BI / Power BI, on els events d'aprovació es converteixen en mètriques netes per a dashboards de productivitat.
Des del punt de vista de la ciberseguretat, la idempotència evita que un atacant pugui repetir una sol·licitud d'aprovació i saturar el sistema de correus o provocar accions no desitjades. Cada petició porta una clau única que el servidor valida, i el registre immutable a la base de dades proporciona un rastre d'auditoria complet. A més, l'ús d'un outbox transaccional impedeix que l'enviament de correu es perdi o dupliqui fins i tot si el worker falla. És un disseny que s'alinea amb els principis de resiliència que apliquem en projectes cloud-native.
Per provar aquest flux abans del llançament, no n'hi ha prou amb fer clics manuals. Recomanem tres verificacions: 1) Test d'API on s'envien dues peticions amb la mateixa clau d'idempotència i es comprova que només es generi un event d'outbox. 2) Test d'UI que simuli un doble clic ràpid i verifiqui que el botó es desactiva i no es produeix una segona crida. 3) Test de recàrrega de pàgina després de l'aprovació, confirmant que no es reenvia res al backend. A Q2BSTUDIO, fem servir aquestes proves com a part de la nostra metodologia per a desenvolupaments amb automatització de processos, garantint que cada release mantingui la qualitat.
L'impacte al negoci és directe: els correus duplicats generen desconfiança i augmenten el volum de suport. Segons l'enquesta State of JavaScript 2024, la fiabilitat i l'experiència del desenvolupador són factors clau en l'adopció d'eines. Un flux d'aprovació que mai falla en les seves notificacions és invisible per a l'usuari, però es nota en l'absència de queixes. Les empreses que externalitzen amb nosaltres el desenvolupament de programari a mida solen esmentar que aquesta estabilitat els permet escalar sense por a incidents de comunicació.
En resum, el camí per evitar bucles d'efectes a React no està en afegir més lògica condicional al frontend, sinó en moure la responsabilitat de l'enviament de correus al backend amb un disseny basat en events i idempotència. Aquesta filosofia, que apliquem a Q2BSTUDIO, s'estén a qualsevol sistema que requereixi notificacions fiables: des d'aprovacions de documents fins a workflows d'agents IA o integracions amb plataformes cloud. La propera vegada que un flux d'aprovació sembli 'embruixat', recorda que la solució més robusta és també la més avorrida: que el frontend només faci clic i el backend decideixi què fer amb aquest clic.




