How to create a ReScript React Hook from a React Hook

Hi There,

I’m trying to create bindings for a TypeScript React library that has developed some useful hooks. Here is an example hook: https://github.com/apibara/starknet-react/blob/main/packages/core/src/hooks/call.ts.

Are hooks something that we can bind as regular JS functions or do I have to rewrite the hook in ReScript directly? Would appreciate if you had some guidelines on how to integrate.

Best
Peteris

cant speak for them all but looking at useStarknetCall it should be pretty much good to go.
The arguments to the hook are passed as an object so you can add a record type for that.
Lots of optionality exposed on the interface which is fine but will be a nuisance to work with.
The contract arg is callable with the args…arg, so you will have some pretty obtuse types there but should work…if you have higher designs on type correctness at that interface it might invite a thicker binding, or a rewrite. (Could pass contract and args as a thunk, for example)

(There is some superstition used with hooks in react but I think after all they are “just functions” that also happen to do careful reference management or call other hooks that do. So theyre really incredibly fragile hence the “Must prefix with use”, “must be called at the top level of your component”. There may be more mechanism there but i havnt seen it, and shouldnt want to see it :wink:

2 Likes

Yes, you can just use ReScript’s normal binding syntax for the hook. I’m not familiar with that library, but here’s one possible approach.

I was following the API docs on the page for useStarknetCall.

module Contract = {
  type t
  @val external make: unit => t = "makeContract" // TODO: Implement
}

module StarknetCall = {
  module Result = {
    type t

    // TODO: `data` is an array of `any` so treating it as JSON for now
    @get external data: t => Js.Nullable.t<array<Js.Json.t>> = "data"
    @get external loading: t => bool = "loading"
    @get external error: t => Js.Nullable.t<string> = "loading"
    @send external refresh: t => unit = "refresh"
  }

  module Parameters = {
    type t

    // TODO: No idea what `args` should be so just using a JS object
    @obj
    external make: (~contract: Contract.t=?, ~method: string=?, ~args: {..}=?, unit) => t = ""
  }

  @module("@starknet-react/core") @val
  external useStartnetCall: Parameters.t => Result.t = "useStartnetCall"
}

// Example usage

let params = StarknetCall.Parameters.make(
  ~contract=Contract.make(),
  ~method="method",
  ~args={"arg1": 0, "arg2": ""},
  (),
)

let result = StarknetCall.useStartnetCall(params)

if StarknetCall.Result.loading(result) {
  Js.log("Loading")
} else {
  let error = StarknetCall.Result.error(result)->Js.Nullable.toOption
  switch error {
  | Some(errorMessage) => Js.log(errorMessage)
  | None => {
      let data = StarknetCall.Result.data(result)->Js.Nullable.toOption
      switch data {
      | None => Js.log("No data")
      | Some(data) => Js.log(data)
      }
    }
  }
}
1 Like