Never ask for consent ever again

Dangerous operations often require a user input. For example, your UI might have a delete button that will destroy some resource, perform an irreversible operation or launch a missile.
In such cases, it is preferable to prompt the application user for consent before performing the dangerous operation.
This article implements a React abstraction that will prevent you from asking for consent ever again.
The valid approach that we want to stop using
In your view:
  • Render a modal component that is controlled by a boolean state. This state controls whether the modal is opened or not.
  • The modal component either calls a callback when the user clicks "Confirm" or implements the logic to perform the operation that requires confirmation.
  • In React pseudo-code:
    const [opened, setOpened] = useState(false);
    const launch = useLaunchMissile();
    
    return (
      <div>
        <button onClick={() => setOpened(true)}>Launch missile</button>
        <ConfirmationModal
          opened={opened}
          onConfirm={launch}
          onClose={() => setOpened(false)}
        />
      </div>
    )
    The problem with this approach is that you have to add code in your UI for each user confirmation.
    A better approach
    It is possible to create an abstraction around prompts, and to inject a method that calls this abstraction.
  • First, we will create an abstraction around our prompts. In React, we can create this with a context and a custom hook:
  • // `./context/DialogProvider`
    import {useState, createContext, useMemo} from 'react';
    
    export const DialogContext = createContext({});
    
    export function DialogProvider({ children }) {
      const [Dialog, setDialog] = useState(); // Dialog has type ReactNode
      const context = useMemo(() => ({ setDialog }), []);
    
      return (
        <>
          <DialogContext.Provider value={context}>{children}</DialogContext.Provider>
          {Dialog}
        </>
      );
    }
    
    // `./hooks/use-dialog.js`
    import { useContext, useCallback, useEffect } from 'react';
    import { DialogContext } from '../context/DialogProvider';
    
    export function useDialog() {
      const { setDialog } = useContext(DialogContext);
      const close = useCallback(() => setDialog && setDialog(null), [setDialog]);
      const add = useCallback((node) => setDialog && setDialog(node), [setDialog]);
    
      useEffect(() => {
        return close;
      }, [close]);
    
      return {
        add,
        close,
      };
    }
    The code above allows us to render a dialog/modal/prompt component from anywhere in the code.
  • Second, we will use the abstraction above to render our prompt from a React hook:
  • // ./hooks/use-user-consent.jsx
    import { useDialog } from './use-dialog';
    import { ConfirmationDialog } from '../components/ConfirmationDialog';
    
    export function useUserConsent() {
      const { add, close } = useDialog();
    
      return () =>
        new Promise((resolve) => {
          const onClose = (accepted) => {
            close();
            resolve(accepted);
          };
    
          add(
            <ConfirmationDialog
              onAccept={() => onClose(true)}
              onDismiss={() => onClose(false)}
            />,
          );
        });
    }
    The code above returns a function that returns a Promise. This promise will resolve to true if the user clicked confirm, and resolve to false otherwise. If you wish to test the code, here is a dumb implementation of the ConfirmationDialog component:
    // `./components/ConfirmationDialog.jsx`
    export function ConfirmationDialog({ onDismiss, onAccept }) {
      return (
        <div>
          <div>Are you sure?</div>
          <button onClick={onAccept}>OK</button>
          <button onClick={onDismiss}>Close</button>
        </div>
      )
    }
  • Ask for consent with our abstraction:
  • // App.js
    import { DialogProvider } from './context/DialogProvider'
    import { ConsentTest } from './components/ConsentTest'
    
    function App() {
      return (
        <DialogProvider>
          <ConsentTest />
        </DialogProvider>
      );
    }
    
    export default App;
    
    // `./components/components/ConsentTest.jsx
    import { useCallback } from "react";
    import { useUserConsent } from "../hooks/use-user-consent";
    
    export function ConsentTest() {
      const hasApproval = useUserConsent();
    
      const callback = useCallback(async () => {
        const userConfirmed = await hasApproval();
        alert(userConfirmed);
      }, [hasApproval]);
    
      return <button onClick={callback}>Test</button>
    }
    Conclusion
    We have just seen a way of abstracting asking for user consent.
    This can easily be extended by adding properties to the "hasApproval" method to have a configurable prompt message.

    27

    This website collects cookies to deliver better user experience

    Never ask for consent ever again