27
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.
In your view:
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.
It is possible to create an abstraction around prompts, and to inject a method that calls this abstraction.
// `./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.
// ./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>
)
}
// 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>
}
We have just seen a way of abstracting asking for user consent.
This can easily be extended by adding properties to the "
This can easily be extended by adding properties to the "
hasApproval
" method to have a configurable prompt message.27