Binding with callback handlers not binding properly to "this"

Hey guys, having an issue with a binding that calls this and I am assuming this is causing me issues as I have seen this:

But I have no idea how to use this, or if its even something I need. All I know is if I modify the generated JS in the bs file from this:

var __x = Curry._1(fattJs.showCardForm, undefined);
__x.then(function (handler) {
  console.log("Form was loaded");
  console.log(handler);
  return Promise.resolve(handler);
});

TO:

fattJs.showCardForm().then(handler => {
  console.log("Form was loaded");
  console.log(handler);
})

It will no longer get an error:
TypeError: Cannot read property 'number' of undefined

Here is my binding so far:

type number
type cvv
type props

@bs.obj
external number: (
  ~id: string=?,
  ~placeholder: string=?,
  ~style: string=?,
  ~type_: string=?,
  ~format_: string=?,
  unit,
) => number = ""

@bs.obj
external cvv: (
  ~id: string=?,
  ~placeholder: string=?,
  ~style: string=?,
  ~type_: string=?,
  unit,
) => cvv = ""

@bs.obj
external props: (~number: number=?, ~cvv: cvv=?, unit) => props = ""

type handler = {
  setTestPan: string => unit,
  setTestCvv: string => unit,
}

type fattJs = {showCardForm: unit => Js.Promise.t<handler>}

@bs.new external create: (string, props) => fattJs = "FattJs"

And here is the JS docs for the SDK we are trying to bind to:

Try explicit uncurrying: https://rescript-lang.org/docs/manual/latest/bind-to-js-function#solution-use-guaranteed-uncurrying

1 Like

To make sure I understand the problem, the reason you need to modify the JS is that curried form does not maintain scope? It passes the showCardForm function reference to the curry function and then later calls it (which gives it no scope).

The problem is that depending on how you use a regular function it might be curried, or it might not, depending on what the compiler is able to infer about the function call.

To guarantee the JS is produced as a direct call maintaining scope, the binding must use uncurried form:
type fattJs = {showCardForm: (. unit) => Js.Promise.t<handler>}

To call this from ReScript code add a . to the call, e.g. fattjs.showCardForm(.)

Alternatively you could make fattJs an opaque type and use a new external to call showCardForm, then regular function calls will always be uncurried.

2 Likes

OMG this is amazing! You were spot on @spyder. I knew it was a this scope issue, but had no idea how to fix it as all the uncurried stuff is over my head.

Is there any more resources to better learn and understand this uncurried concept? Something that will by the end force me to understand (.) and (. unit) means?

Thanks a TON - this was a big road block for me.

The link @woeps posted is a good place to start as it’s relevant to your use case, and also the uncurried function docs. You don’t really need to understand what it’s doing, though, just know that in cases where you require the JS to not be curried add a . to the function definition; it works for regular functions as well as externals.

let myUncurriedFunction = (. num) => num + 3
Js.log(myUncurriedFunction(. 3))

In this specific scenario however you might find the external style easier to follow than hand-crafting the correct record syntax style. Here is how to adjust your code for that:

type fattJs

@bs.send external showCardForm: (fattJs, unit) => Js.Promise.t<handler> = "showCardForm"

And then instead of fattJs.showCardForm(.) call it with fattJs->showCardForm (this scenario is one of the reasons -> was created, so that it looks about the same if you squint).

The external is more idiomatic ReScript style, too, where functions are static rather than instanced fields (but it compiles to an instanced field reference thanks to @bs.send).

1 Like