Hello
For some time I’m trying to build an abstraction that would enforce component architecture. I want to mimic a bit of Elm architecture (just a little bit) but I feel like I hit a wall with OCaml/Rescript type system.
Attempt 1
I wrote some abstractions:
module type Sandbox = {
type model
type msg
type props
let initialModel: model
let update: (model, msg) => model
let view: (msg => unit, model, props) => React.element
}
let useSandbox = (sandbox: module(Sandbox)) => {
let module(Sandbox) = sandbox
let (model, dispatch) = React.useReducer(Sandbox.update, Sandbox.initialModel)
Sandbox.view(dispatch, model)
}
It ended up with Sandbox.props
constructor trying to escape its scope.
I learned what it means by some OCaml examples but it is a bit unfortunate because it mismatches with what I wanted to do in the first place… and I just wanted to make clear, what kind of argument I expect from the function returned by Sanbox.view
.
Attempt 2
So I tried this:
module type Sandbox = {
type model
type msg
let initialModel: model
let update: (model, msg) => model
let view: (msg => unit, model, 'props) => React.element
}
let useSandbox = (sandbox: module(Sandbox)) => {
let module(Sandbox) = sandbox
let (model, dispatch) = React.useReducer(Sandbox.update, Sandbox.initialModel)
Sandbox.view(dispatch, model)
}
I just remove the props type constructor and tried generic type… Unfortunately, it works as far as I won’t try to use props.
With view
defined this way:
let view = (dispatch, model, props) => {
<div>
<h1> {text("Actual " ++ props.prefix ++ string_of_int(model.value))} </h1>
{button(_ => dispatch(Add), "Add")}
<button onClick={_ => dispatch(Remove)}> {text("Remove")} </button>
<button onClick={_ => props.switchPrefix()}> {text("Switch Prefix")} </button>
</div>
}
I’m getting this error:
Values do not match:
let view: (msg => unit, model, props) => React.element
is not included in
let view: (msg => unit, model, 'props) => React.element
Okay… I’m a bit confused here, I’m passing some concrete type but it wants… generic type?
Attempt 3
I tried a bit more, I get back to declare type props
in Sandbox
module, switch from simple function to functor:
module MakeSandbox = (Item: Sandbox) => {
let useSandbox = (props) => {
let (model, dispatch) = React.useReducer(Item.update, Item.initialModel)
Item.view(dispatch, model, props)
}
}
and
@react.component
let make = (~prefix: string, ~switchPrefix: unit => unit) => {
module Sanbox = Browser.MakeSandbox(T)
Sanbox.useSandbox({prefix, switchPrefix})
}
I finally achieved my goal (while writing this post ). Initially, I was thinking about moving components make
and makeProps
to functor as well but I’m not sure if it’s even possible and I’m happy with the result so far.
But! After I failed in the first 2 attempts now my brain itches me because:
- I feel like there might solution to the first attempt, but I’m just not able to handle it
- I don’t understand how to deal (or why I can’t deal) with a second attempt
Are you able to help me explain that?