Fila de renderização em componentes ReactJS. 😱
Em diversos projetos que já trabalhei, sempre houve um momento que múltiplos componentes de Modais (janela que exibe um conteúdo adicional em uma camada acima da página atual) eram exibidos simultaneamente na tela. Geralmente são componentes que tratam de assuntos diferentes e que não possuem conhecimento da existência dos outros componentes, assim dificultando a maneira de tratar as condições.
Isso causa uma má experiência para o usuário que está acessando naquele momento.
Mas não é só fazer algumas funções para controlar?
function Page() {
const [modal1, setModal1] = useState(false);
const [modal2, setModal2] = useState(false);
const openModal1 = () => setModal1(true);
const closeModal1 = () => {
setModal1(false);
openModal2();
}
const openModal2 = () => setModal2(true);
const closeModal2 = () => setModal2(false);
return (
<>
{modal1 && <Modal1 />}
{modal2 && <Modal2 />}
</>
);
}
De fato funciona, mas será que é a melhor alternativa?
E se for necessário adicionar mais um componente?
function Page() {
const [modal1, setModal1] = useState(false);
const [modal2, setModal2] = useState(false);
const [modal3, setModal3] = useState(false);
const openModal1 = () => setModal1(true);
const closeModal1 = () => {
setModal1(false);
openModal2();
}
const openModal2 = () => setModal2(true);
const closeModal2 = () => {
setModal2(false);
openModal3();
}
const openModal3 = () => setModal3(true);
const closeModal3 = () => setModal3(false);
return (
<>
{modal1 && <Modal1 />}
{modal2 && <Modal2 />}
{modal3 && <Modal3 />}
</>
);
}
Cada vez mais extenso... Mas claro que há outras maneiras de se fazer isso, da forma acima talvez seja a mais "popular" pela simplicidade.
OK, como posso fazer diferente?
O ponto é que conseguimos ter o mesmo resultado de uma forma mais simples, sem ter que controlar múltiplos states ou ficar fazendo diversas condições.
Primeiro, criamos um componente para controlar a fila de exibição:
// queue.tsx
import { cloneElement, ReactElement, useState } from "react";
type Props = {
components: ReactElement[];
}
export default function QueueComponents({ components }: Props) {
const [queue, setQueue] = useState<ReactElement[]>(components);
const [current, setCurrent] = useState<ReactElement | null>(components?.[0] ?? null);
function processQueue() {
const newQueue = queue.slice(1);
setQueue(newQueue);
setCurrent(newQueue[0] ?? null);
};
if (!current) return null;
return cloneElement(current, { processQueue });
}
Basicamente a função dele é inserir os componentes dentro de uma fila (state) e controlar quem deve ser renderizado (de acordo com a ordem inserida). Para a renderização, clonamos o componente filho acrescentando mais uma prop processQueue
, para servir de callback.
Conseguimos também aprimorar nosso componente para saber quando não há mais componentes na fila e executar uma ação.
function processQueue() {
const newQueue = queue.slice(1);
if (!newQueue.length) {
console.log('fila processada, faça algo...');
setQueue([]);
setCurrent( null);
return;
}
setQueue(newQueue);
setCurrent(newQueue[0]);
};
Com isso, os componentes que são passados para a fila, conseguem acessar essa nova prop:
// modal.tsx
function Modal({ processQueue }) {
function onClose() {
processQueue();
}
return (
<div className="modal">
<button onClick={onClose}>Fechar modal</button>
</div>
)
}
E no arquivo que chama os componentes simplificamos desta forma:
// app.tsx
function App() {
return (
<QueueComponents
components={
[
<Modal1 {...props1} />,
<Modal2 {...props2} />,
<Moda3 {...props3} />
]
}
/>
)
}
Pronto, fácil né? Isso também pode ser utilizado em outros casos, como por exemplo um Tour (apresentação) de elementos em uma página.
Desta forma conseguimos sempre ter uma ordem de exibição para os componentes, assim não causando múltiplas renderizações desnecessárias.
🙂