Diálogos Prometidos

O que é uma caixa de diálogo?

A Wikipedia diz o seguinte:
Uma caixa de diálogo (caixa de diálogo em inglês) em uma interface gráfica do usuário é um elemento especial da interface, uma janela projetada para exibir informações e (ou) receber uma resposta do usuário. Ele recebeu esse nome porque realiza uma interação bidirecional entre usuário e computador ("diálogo"): dizendo algo ao usuário e aguardando uma resposta dele.

Estamos interessados ​​em
esperando por uma resposta dele
Em outras palavras, abrimos uma janela modal para obter feedback e fazer algo depois disso. Não se parece com nada? E eu pensei que sim.

Imagine uma situação, temos um aplicativo para gerenciamento de usuários.
O cenário é o seguinte.

Na página principal, o usuário pode abrir uma janela modal para uma lista de outros usuários.
Na lista, você pode abrir uma janela modal com informações sobre o usuário; também nesta janela, há um formulário para o envio de cartas.

Ao enviar uma carta, o usuário abre uma janela modal sobre o envio bem-sucedido.
Quando o usuário fecha a janela modal, ele volta para a janela modal da lista de usuários, mas há um botão para escrever outra letra; quando clicado, o usuário acessa a página do usuário.

Para ler, é bastante difícil imaginar essa tarefa na forma de um diagrama de seqüência.

imagem

Agora tudo é muito mais simples.

Do ponto de vista do código, abrir uma janela modal é uma ação síncrona, podemos abri-la, dentro dela, fechar, mas e se alterar dados na janela modal, ao fechá-la, você precisa obter dados a partir daí?

Um exemplo simples: a partir da janela modal do usuário, alteramos os dados, retornando à janela modal da lista, você precisa atualizar as informações sobre esse usuário.

Cheira a assincronismo ...

Quando abrimos o módulo, precisamos aguardar o fechamento e obter os dados inseridos pelo usuário. Ações assíncronas são muito bem implementadas com promessas.

De fato, as promessas já estão estabelecidas em nosso diagrama, apenas as marcamos como ações. Você pode refazer um pouco.

imagem

Agora tudo se torna simples quando o usuário abre a janela modal, esperamos até que ele termine seu trabalho, após o qual a resolução é chamada na promis. Parece simples, vamos começar.

Minha estrutura principal é uma reação, por isso faremos imediatamente com base nela. Para poder abrir janelas modais de qualquer parte do aplicativo, usaremos a API de contexto.

Primeiro de tudo, precisamos criar um contexto e um local onde será armazenado.

// ./Provider.js export const DialogContext = React.createContext(); export const Provider = ({ children, node, Layout, config }) => { const [instances, setInstances] = useState([]); const [events, setEvents] = useState([]); const context = { instances, setInstances, config, events, setEvents }; const Component = instances.map(instance => ( <Layout key={instance.instanceName} component={config[instance.instanceName]} {...instance} /> )); const context = { instances setInstances }; //   state     const child = useMemo(() => React.Children.only(children), [children]); return ( <DialogContext.Provider value={context}> <> {child} {createPortal(Component, node)} </> </DialogContext.Provider> ); }; 

Tudo é simples aqui, usamos o primeiro useState para criar uma matriz de janelas modais abertas. Algo como uma pilha.

O segundo, useState, é necessário para adicionar referências para resolver e rejeitar a promessa. Isso vamos ver abaixo.

Redirecionamos a renderização pelo portal para que não precisemos lutar se algo acontecer com o z-index.

O layout é um componente que será o componente base para todas as janelas modais.

O parâmetro config é apenas um objeto, em que a chave é o identificador da janela modal e o valor é o componente da janela modal.

 //  config.js export const exampleInstanceName = 'modal/example'; export default { [exampleInstanceName]: React.lazy(() => import('./Example')), }; 

Agora, escrevemos uma implementação do método que abrirá janelas modais.

Este será o gancho:

 export const useDialog = () => { const { setEvents, setInstances, config } = useContext(DialogContext); const open = instance => new Promise((resolve, reject) => { if (instance.instanceName in config) { setInstances(prevInstances => [...prevInstances, instance]); setEvents(prevEvents => [...prevEvents, { resolve, reject }]); } else { throw new Error(`${instance['instanceName']} don't exist in modal config`); } }); return { open }; }; 

O gancho retorna uma função aberta que podemos usar para chamar uma janela modal.

 import { exampleInstanceName } from './config'; import { useDialog } from './useDialog'; const FillFormButton = () => { const { open } = useDialog(); const fillForm = () => open(exampleInstanceName) return <button onClick={fillForm}>fill form from modal</button> } 

Nesta opção, nunca esperaremos que a janela modal seja fechada; precisamos adicionar métodos para concluir a promessa:

 // ./Provider.js export const DialogContext = React.createContext(); export const Provider = ({ children, node, Layout, config }) => { const [instances, setInstances] = useState([]); const [events, setEvents] = useState([]); const close = useCallback(() => { const { resolve } = events[events.length - 1]; const resolveParams = { action: actions.close }; setInstances(prevInstances => prevInstances.filter((_, index) => index !== prevInstances.length - 1)); setEvents(prevEvents => prevEvents.filter((_, index) => index !== prevEvents.length - 1)); resolve(resolveParams); }, [events]); const cancel = useCallback((values): void => { const { resolve } = events[events.length - 1]; const resolveParams = { action: actions.cancel, values }; setInstances(prevInstances => prevInstances.filter((_el, index) => index !== prevInstances.length - 1)); setEvents(prevEvents => prevEvents.filter((_el, index) => index !== prevEvents.length - 1)); resolve(resolveParams); }, [events]); const success = useCallback((values) => { const { resolve } = events[events.length - 1]; const resolveParams = { action: actions.success, values }; setInstances(prevInstances => prevInstances.filter((_el, index) => index !== prevInstances.length - 1)); setEvents(prevEvents => prevEvents.filter((_el, index) => index !== prevEvents.length - 1)); resolve(resolveParams); }, [events]); const context = { instances, setInstances, config, events, setEvents }; const Component = instances.map(instance => ( <Layout key={instance.instanceName} component={config[instance.instanceName]} cancel={cancel} success={success} close={close} {...instance} /> )); const context = { instances setInstances }; //   state     const child = useMemo(() => React.Children.only(children), [children]); return ( <DialogContext.Provider value={context}> <> {child} {createPortal(Component, node)} </> </DialogContext.Provider> ); }; 

Agora, quando no componente Layout ou se passar esses métodos para o componente da janela modal, os métodos de sucesso, cancelamento ou fechamento serão chamados, teremos a resolução com a promessa necessária. Aqui, um conceito como ação é adicionado; essa é uma linha que responde em que status o diálogo foi concluído. Isso pode ser útil quando executamos alguma ação após a execução da janela modal:

 const { useState } from 'rect'; import { exampleInstanceName } from './config'; import { useDialog } from './useDialog'; const FillFormButton = () => { const [disabled, setDisabled] = useState(false); const { open } = useDialog(); const fillForm = () => open(exampleInstanceName) .then(({ action }) => { if (action === 'success') setDisabled(true); }); return <button onClick={fillForm} disabled={disabled}>fill form from modal</button> } 

Isso é tudo. Resta adicionar a transferência de parâmetros da janela modal para a janela modal da função aberta. Bem, acho que você pode lidar com isso sozinho, mas se você for muito preguiçoso, há um pacote pronto que você pode usar em seus projetos.

Source: https://habr.com/ru/post/pt470824/


All Articles