DEV Community

Tom Slutsky
Tom Slutsky

Posted on

Render Hook Pattern in React

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)

Collapse
 
hobo1618 profile image
Will

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...

Collapse
 
tomslutsky profile image
Tom Slutsky

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.

Collapse
 
daveteu profile image
Dave

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...