Hey, I’m still trying to get comfortable with the JS interop. Was wondering if anyone has an example of adding an event listener (bonus points if it listens for a window resize). I’d typically use this to build a responsive React component that switches between mobile, tablet, desktop etc. This would be a typical example of the hook that I’d use:
Since the reason for this post was about building a responsive builder component, I thought I’d provide my full solution
Starting off, Responsive.res, this module defines the hook that listens for window resizes, and exposes a component that sends the screen width down the context.
//Responsive.res
@val @scope("window")
external addEventListener: (string, unit => unit) => unit = "addEventListener"
@val @scope("window")
external removeEventListener: (string, unit => unit) => unit = "removeEventListener"
@val @scope("window")
external innerWidth: int = "innerWidth"
let useViewport = () => {
let (width, setWidth) = React.useState(() => innerWidth)
React.useEffect(() => {
let handleWindowResize = () => {
setWidth(_ => innerWidth)
}
addEventListener("resize", handleWindowResize)
let cleanUp = () => removeEventListener("resize", handleWindowResize)
Some(cleanUp)
})
width
}
let context = React.createContext(0)
module WidthProvider = {
let provider = React.Context.provider(context)
@react.component
let make = (~value, ~children) => {
React.createElement(provider, {"value": value, "children": children})
}
}
module Provider = {
@react.component
let make = (~children: React.element) => {
let width = useViewport()
<WidthProvider value=width> children </WidthProvider>
}
}
let provider = Provider.make
My .resi file looks like this:
//Responsive.resi
let provider: {"children": React.element} => React.element
let context: React.Context.t<int>
only exposing the context and the Provider.make (this may seem strange but will show why soon, of course you can expose as much as you want here).
Then I have a ResponsiveBuilder component:
//ResponsiveBuilder.res
open Belt.Option
@react.component
let make = (
~xs: React.element,
~sm: option<React.element>=?,
~md: option<React.element>=?,
~lg: option<React.element>=?,
~xl: option<React.element>=?,
) => {
let width = React.useContext(Responsive.context)
if width >= 1920 && xl->isSome {
xl->getExn
} else if width >= 1280 && lg->isSome {
lg->getExn
} else if width >= 960 && md->isSome {
md->getExn
} else if width >= 600 && sm->isSome {
sm->getExn
} else {
xs
}
}
This component is based on the Material-UI concept of “mobile-first”. This means you have to pass in the xs component, but everything after that is optional. Not sure if there are some cleaner ways to write this piece of logic? Eg. pattern matching or without the getExn?
Finally, I have a ProviderWrapper component that I use at the top level of my app: