DEV Community

Sébastien Belzile
Sébastien Belzile

Posted on • Edited on

2 1

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>
)
Enter fullscreen mode Exit fullscreen mode

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.

  1. 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,
  };
}
Enter fullscreen mode Exit fullscreen mode

The code above allows us to render a dialog/modal/prompt component from anywhere in the code.

  1. 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)}
        />,
      );
    });
}
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode
  1. 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>
}

Enter fullscreen mode Exit fullscreen mode

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.

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (1)

Collapse
 
joelnwalkley profile image
Joel N. Walkley

Oooh, interesting. I JUST wrote a confirmation modal in two different places (😔, I know I know!). Bookmarking to try to help my refactoring.

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay