I found myself the other day trying to build a dropdown component in Reason that on outside click would close the menu if opened.
The only example I could find of this was in Reason from GlennSL and it was a bit outdated since it was written in 2018, before hooks were fully implemented into reason. The code below is an updated version using hooks, it requires the bs-dependency
of bs-webapi
so make sure to add that before you use it.
[@genType "OnClickOutside"]
[@react.component]
let make = (~onOutsideClick, ~children) => {
let outsideContainer = React.useRef(Js.Nullable.null);
React.useEffect0(() => {
open Webapi.Dom;
let onClick = e => {
let target = MouseEvent.target(e);
let outsideDiv =
Belt.Option.getExn(
Js.Nullable.toOption(React.Ref.current(outsideContainer)),
);
let targetElement = EventTarget.unsafeAsElement(target);
if (!Element.contains(targetElement, outsideDiv)) {
onOutsideClick();
};
};
Document.addClickEventListener(onClick, document);
Some(() => Document.removeClickEventListener(onClick, document));
});
<div ref={outsideContainer->ReactDOMRe.Ref.domRef}> children </div>;
};
The logic breakdown:
- wrap the component in a div with a ref
- add an event listener for onClick
- in that onClick function check whether the element clicked contains the div with the ref, if it does then the click is inside the div. If not then the click is outside of the div.
- If the click is outside of the container then do whatever you need it to(in this case above, close the menu)
In the use case above we wrap our select component in the OnClickOutside
and onOutsideClick close the dropdown menu container like so;
let (visible, setVisibility) = React.useState(() => false);
<OnClickOutside onOutsideClick={_e => setVisibility(_ => false)}>
<Select.Button
toggled=visible onClick={_e => setVisibility(_ => !visible)} />
<Select.List name toggled=visible />
</OnClickOutside>
If you enjoy writing Reason and would like to write more of it, you're in luck. Draftbit is hiring, we're a no-code tool that let's users build cross-platform apps and we're built mostly in reason! Check us out!
UPDATE:
I was experiencing some state issues with the trigger so I added an extra useEffect to fix this.
[@genType "OverlayTrigger"]
[@react.component]
let make = (~onClick, ~children) => {
let outsideContainer = React.useRef(Js.Nullable.null);
open Webapi.Dom;
let onClickHandler = event => {
let target = MouseEvent.target(event);
let outsideDiv =
Belt.Option.getExn(
Js.Nullable.toOption(React.Ref.current(outsideContainer)),
);
let targetElement = EventTarget.unsafeAsElement(target);
if (!Element.contains(targetElement, outsideDiv)) {
onClick();
};
};
React.useEffect2(
() => {
Document.addMouseDownEventListener(onClickHandler, document);
Some(
() => Document.removeMouseDownEventListener(onClickHandler, document),
);
},
(onClick, React.Ref.current(outsideContainer)),
);
<div
className="cursor-pointer w-full"
ref={outsideContainer->ReactDOMRe.Ref.domRef}>
children
</div>;
};
Top comments (0)