Working on a React code base I found my self in a need to display many confirmation modals.
I got frustrated after the third one and found a nifty pattern to use: returning the modal component itself from a hook.
I assume that a this point in time there is no longer a need to introduce the concept of hooks in React. However if you need a refresher you might want to check https://reactjs.org/docs/hooks-intro.html
So my basic Idea was to use an API that will look roughly like that:
const ComponentWithDangerAction = () => {
const [Modal, onClick] = useConfirm({
onConfirm: ({ id }) => alert("deleted item id: " + id),
onDismiss: alert,
title: "Danger Zone",
message: "This action is irreversible. Are you sure you want to continue?"
});
return (
<div className="App">
<Modal />
<Button onClick={() => onClick({ id: 5 })}>
Press here to delete something important
</Button>
</div>
);
};
Next step is to create the useConfirm hook itself and it easiest of course to start with a minimal non crashing api (assuming we have a Modal component).
const useConfirm = () => {
const onClick = () => null;
const ConfirmModal = () => <Modal />
return [ConfirmModal, onClick];
}
Now adding disclosure related state and callbacks functionality
const useConfirm = ({onConfirm, onDismiss, message }) => {
const [isOpen, setOpen] = useState(false);
const toggle = setOpen(!isOpen);
const onClick = () => toggle();
const handleConfirm = () => {
onConfirm && onConfirm();
toggle();
}
const handleDismiss = () => {
onDismiss && onDismiss();
toggle();
}
const ConfirmModal = () => (
<Modal isOpen={isOpen} onClose={toggle}>
<span>{message}</span>
<button onClick={handleConfirm}>Confirm</button>
<button onClick={handleDismiss}></Dismiss>
</Modal>)
return [ConfirmModal, onClick];
}
Almost Done! The only problem is I want to be able to pass arguments to the confirm function (I want to delete a specific item from a list).
My solution was to store arguments passed to onClick to the state of the hook. That way when Confirm button is pressed I can call the onConfirm callback with the arguments passed to it.
const useConfirm = ({onConfirm, onDismiss, message }) => {
const [isOpen, setOpen] = useState(false);
const [confirmArgs, setConfirmArgs] = useState(false);
const toggle = setOpen(!isOpen);
const onClick = (args) => {
setConfirmArgs(args); // storing the args
};
const handleConfirm = () => {
onConfirm && onConfirm(confirmArgs); // using the args
toggle();
}
const handleDismiss = () => {
onDismiss && onDismiss();
toggle();
}
const ConfirmModal = () => (
<Modal isOpen={isOpen} onClose={toggle}>
<span>{message}</span>
<button onClick={handleConfirm}>Confirm</button>
<button onClick={handleDismiss}></Dismiss>
</Modal>)
return [ConfirmModal, onClick];
}
Hope you will find this pattern useful :)
you can find a more complete example on codesandbox
And of course follow me on twitter @SlutskyTom
Top comments (3)
Hey! Thanks for posting. I'm just getting my head around hooks, so this was certainly interesting. I was just wondering if there are any performance issues when you return a component inside a hook like this. I found this thread on reddit, but not sure if it applies here. Any thoughts? reddit.com/r/reactjs/comments/9yq1...
Thanks for responding! glad you found it useful :)
It pretty much the same as rendering the component and controlling it with state held by the parent component.
I actually had a doubt about it because I needed the modal for alerting the user before deleting item from a list, So it looked like I will need a Modal for each item. However I ended up using only one modal component by introducing the ability to pass arguments to the function toggling the modal.
I case you found the performance not sufficient enough (which I really doubt), I guess you can use context provider to render the modal only once in your component tree and controlling it with a hook.
If it sounds interesting to you let me know and I might write an example of that.
This is not bad. I've approached this problem in a different way, using Promises. Let me know how you feel about my take.
daveteu.medium.com/react-custom-co...