Implementando Modal de Confirmação Reutilizável com React

vitorrios1001

Vitor Rios

Posted on June 27, 2024

Implementando Modal de Confirmação Reutilizável com React

Introdução

Modais de confirmação são componentes essenciais em muitas aplicações web, permitindo que os usuários confirmem ações críticas antes de prosseguir. Neste artigo, apresento uma solução prática para criar um modal de confirmação reutilizável em React. A abordagem utiliza um contexto para gerenciar o estado do modal e permite personalizar facilmente o título, subtítulo e textos dos botões. Esta solução foi desenvolvida com base em várias fontes e aprimorada para oferecer um fluxo de confirmação eficiente e consistente.

Estrutura do Projeto

A estrutura básica do projeto inclui os seguintes arquivos:

  • confirmation-modal.tsx
  • confirmation-modal.context.tsx
  • use-confirmation-modal.ts
  • types.ts
  • App.tsx
  • index.tsx

Componente de Modal de Confirmação

O componente ConfirmationModal é responsável por renderizar o modal com base nas propriedades recebidas. Utiliza o Modal do Ant Design para a estrutura do modal.

import { Modal } from "antd";
import React, { useEffect } from "react";
import { useConfirmationModal } from "./use-confirmation-modal";

interface IConfirmationModalProps {
  children?: React.ReactNode;
}

const ConfirmationModal = ({ children }: IConfirmationModalProps) => {
  const { isOpen, confirmationArgs, actions } = useConfirmationModal();

  const handleCancel = () => actions.cancel?.();

  const handleConfirm = () => actions.proceed?.();

  useEffect(() => {
    if (!actions.proceed) return;

    const handleKeydown = (e: any) => {
      if (actions.proceed && isOpen && e.key === "Enter") {
        actions.proceed();
      }
    };

    window.addEventListener("keydown", handleKeydown);

    return () => window.removeEventListener("keydown", handleKeydown);
  }, [actions.proceed, isOpen]);

  return (
    <Modal
      title={confirmationArgs.title}
      okText={confirmationArgs.confirmActionText || "Confirm"}
      cancelText={confirmationArgs.cancelActionText || "Cancel"}
      onCancel={handleCancel}
      onOk={handleConfirm}
      open={isOpen}
      width={640}
    >
      <div>
        <p>{confirmationArgs.subtitle}</p>
        {children}
      </div>
    </Modal>
  );
};

export default ConfirmationModal;
Enter fullscreen mode Exit fullscreen mode

Explicação Detalhada do Componente

  1. Importações: Importamos Modal do Ant Design e useEffect do React para gerenciar o ciclo de vida do componente.
  2. Props: Definimos IConfirmationModalProps para aceitar children, permitindo flexibilidade na renderização do conteúdo do modal.
  3. Hook Personalizado: Utilizamos useConfirmationModal para acessar o estado e as ações do contexto de confirmação.
  4. Funções de Manipulação: handleCancel e handleConfirm chamam as funções de cancelamento e confirmação armazenadas no contexto.
  5. Efeito Colateral: Usamos useEffect para adicionar e remover um ouvinte de eventos de teclado que aciona a confirmação ao pressionar a tecla Enter.
  6. Renderização do Modal: Utilizamos o componente Modal do Ant Design para exibir o modal, personalizando o título, texto dos botões e conteúdo com base nos argumentos de confirmação.

Contexto para Gerenciamento de Estado

O contexto ConfirmationModalContext gerencia o estado do modal, incluindo se está aberto, os argumentos de confirmação e as ações de confirmar e cancelar.

import { createContext, ReactNode, useState } from "react";
import ConfirmationModal from "./confirmation-modal";
import { IActions, IConfirmationArgs, IContextType } from "./types";

const INITIAL_CONFIRMATION_STATE: IConfirmationArgs = {
  title: "",
  subtitle: "",
  confirmActionText: "",
  cancelActionText: "",
};

export const ConfirmationModalContext = createContext<IContextType>({
  actions: {
    proceed: null,
    cancel: null,
  },
  confirmationArgs: INITIAL_CONFIRMATION_STATE,
  isOpen: false,
  isConfirmed: () => Promise.resolve(true),
});

interface IProviderProps {
  children: ReactNode;
}

export function ConfirmationModalProvider({ children }: IProviderProps) {
  const [actions, setActions] = useState<IActions>({
    proceed: null,
    cancel: null,
  });

  const [isOpen, setIsOpen] = useState(false);

  const [confirmationArgs, setConfirmationArgs] = useState<IConfirmationArgs>(
    INITIAL_CONFIRMATION_STATE
  );

  const isConfirmed = (confirmationArgsData: Partial<IConfirmationArgs>) => {
    const promise = new Promise((resolve, reject) => {
      setActions({ proceed: resolve, cancel: reject });
      setConfirmationArgs({
        ...INITIAL_CONFIRMATION_STATE,
        ...confirmationArgsData,
      });
      setIsOpen(true);
    });

    return promise.then(
      (): boolean => {
        setIsOpen(false);
        return true;
      },
      (): boolean => {
        setIsOpen(false);
        return false;
      }
    );
  };

  return (
    <ConfirmationModalContext.Provider
      value={{ isOpen, isConfirmed, confirmationArgs, actions }}
    >
      <ConfirmationModal />
      {children}
    </ConfirmationModalContext.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Explicação Detalhada do Contexto

  1. Estado Inicial: INITIAL_CONFIRMATION_STATE define o estado inicial das propriedades do modal de confirmação.
  2. Criação do Contexto: ConfirmationModalContext cria o contexto que armazenará o estado e as ações do modal.
  3. Provider: ConfirmationModalProvider é o componente que encapsula a lógica de estado do modal e fornece o contexto para seus filhos.
  4. Função isConfirmed: Esta função aceita argumentos de confirmação, atualiza o estado do modal e retorna uma promessa que resolve ou rejeita com base na ação do usuário.

Hook para Acesso ao Contexto

O hook useConfirmationModal facilita o acesso ao contexto dentro de outros componentes.

import React from "react";
import { ConfirmationModalContext } from "./confirmation-modal.context";

export function useConfirmationModal() {
  const { isOpen, isConfirmed, confirmationArgs, actions } = React.useContext(
    ConfirmationModalContext
  );
  return { isOpen, isConfirmed, confirmationArgs, actions };
}
Enter fullscreen mode Exit fullscreen mode

Explicação Detalhada do Hook

  1. Importações: Importamos React e ConfirmationModalContext.
  2. Uso do Contexto: useConfirmationModal utiliza React.useContext para acessar e retornar o estado e as ações do contexto.

Definição de Tipos

Definimos os tipos necessários para as propriedades e estado do modal em types.ts.

import { ButtonProps } from "antd";

export interface IActions {
  proceed: null | ((value?: unknown) => void);
  cancel: null | ((value?: unknown) => void);
}

export interface IConfirmationArgs {
  title: string;
  subtitle: string;
  confirmActionText?: string;
  cancelActionText?: string;
  customCancelAction?: () => void;
  confirmButtonCustomType?: ButtonProps["type"];
  cancelButtonCustomType?: ButtonProps["type"];
}

export interface IContextType {
  actions: IActions;
  confirmationArgs: IConfirmationArgs;
  isOpen: boolean;
  isConfirmed: (
    confirmationArgsData: Partial<IConfirmationArgs>
  ) => Promise<boolean>;
}
Enter fullscreen mode Exit fullscreen mode

Explicação Detalhada dos Tipos

  1. IActions: Define as funções de ação de proceder e cancelar.
  2. IConfirmationArgs: Define os argumentos de configuração do modal, incluindo título, subtítulo e textos dos botões.
  3. IContextType: Define o formato do contexto, incluindo ações, argumentos de confirmação, estado de visibilidade e função isConfirmed.

Componente Principal

O componente App demonstra como usar o modal de confirmação para ações como deletar um item ou resetar uma lista.

import { Button } from "antd";
import { useState } from "react";
import { useConfirmationModal } from "./components/confirmation-modal";
import "./styles.css";

const INITIAL_STATE = ["banana", "watermelon", "grape", "orange"];

export default function App() {
  const [list, setList] = useState(INITIAL_STATE);

  const { isConfirmed } = useConfirmationModal();

  const handleDelete = async (fruit: string) => {
    const willDelete = await isConfirmed({
      title: "Delete Fruit",
      subtitle: `Are you sure you want to delete the ${fruit}?`,
      confirmActionText: `Delete ${fruit}`,
    });

    if (!willDelete) return;

    setList((prev) => prev.filter((item) => item !== fruit));
  };

  const reset = async () => {
    const willReset = await isConfirmed({
      title: "Reset",
      subtitle: `Are you sure you want to reset the list?`,
      confirmActionText: `Reset List`,
    });

    if (!willReset) return;

    setList(INITIAL_STATE);
  };

  return (
    <div className="App">
      <h1>Confirmation Modal</h1>
      <h2>POC to confirmation modal flow!</

h2>

      <Button type="primary" onClick={reset}>
        Reset
      </Button>

      <ul>
        {list.map((fruit) => {
          return (
            <li key={fruit}>
              <Button type="default" onClick={() => handleDelete(fruit)}>
                X
              </Button>{" "}
              {fruit}
            </li>
          );
        })}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Explicação Detalhada do Componente Principal

  1. Estado Inicial: INITIAL_STATE define a lista inicial de itens.
  2. Estado da Lista: Utilizamos useState para gerenciar a lista de itens.
  3. Uso do Hook: Utilizamos useConfirmationModal para acessar a função isConfirmed.
  4. Função handleDelete: Mostra o modal de confirmação e remove o item da lista se a confirmação for positiva.
  5. Função reset: Mostra o modal de confirmação e reseta a lista se a confirmação for positiva.
  6. Renderização: Renderiza um botão para resetar a lista e uma lista de itens com botões para deletar cada item.

Ponto de Entrada da Aplicação

Por fim, configuramos o ponto de entrada da aplicação para usar o ConfirmationModalProvider.

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { ConfirmationModalProvider } from "./components/confirmation-modal";

const rootElement = document.getElementById("root")!;
const root = ReactDOM.createRoot(rootElement);

root.render(
  <React.StrictMode>
    <ConfirmationModalProvider>
      <App />
    </ConfirmationModalProvider>
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

Explicação Detalhada do Ponto de Entrada

  1. Importações: Importamos React, ReactDOM, App e ConfirmationModalProvider.
  2. Configuração do Root: Criamos o root element e configuramos o renderizador do React.
  3. Renderização: Envolvemos o App com ConfirmationModalProvider para fornecer o contexto a toda a aplicação.

Benefícios desta Abordagem

  • Reutilização: O modal de confirmação é reutilizável em toda a aplicação, evitando duplicação de código.
  • Flexibilidade: É fácil personalizar o conteúdo e o comportamento do modal para diferentes cenários.
  • Simplicidade: A lógica de exibição e personalização do modal é centralizada, tornando o código mais fácil de manter.
  • Consistência: Garante uma experiência de usuário consistente ao usar o mesmo modal para diferentes ações de confirmação.

Conclusão

Modais de confirmação são componentes importantes para a interação do usuário com ações críticas. Implementar um modal de confirmação reutilizável e personalizável, como mostrado neste artigo, melhora a consistência e a eficiência da aplicação. Esta solução aproveita o poder dos hooks do React e o contexto para gerenciar o estado do modal, oferecendo uma abordagem flexível e escalável para confirmar ações do usuário.

Espero que esta implementação ajude você a adicionar modais de confirmação eficazes em suas aplicações React. Sinta-se à vontade para adaptar e expandir esta solução conforme necessário para atender às suas necessidades específicas.

Você pode conferir o código completo e funcional no CodeSandbox através do link.

💖 💪 🙅 🚩
vitorrios1001
Vitor Rios

Posted on June 27, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related