Preact signals-react bindings (basic)

Well, I wanted to see what all the noise was about regarding signals but the options for integrating into ReScript seemed sparse.

There are the issues with SolidJS that have been discussed in other posts. I tried to use the existing bindings but I couldn’t get it working with v11 (and I really wasn’t keen on adding Babel anyway).

I saw that some people were using Preact by aliasing React to Preact, but the question remained as to whether you could use Preact signals (although I would guess not in that fashion as React doesn’t have a signals library that could be aliased).

The I saw that Preact has a library that allows you to integrate signals into React. I really wanted to get a handle on creating bindings so I thought this would be good practice.

Obviously my example will be pretty much a toy app and I can’t speak for how useful (or useless) signals are in React, but just figured I’d share my starter set of bindings. I opted for the hooks integration instead of the Babel transform integration. Preact recommends the Babel transform method, but I figured creating bindings is challenging enough with having to worry about adding Babel config drama.

Preact.res:

module Signal = {
  type t

  @module("@preact/signals-react") @new external make: 'value => t = "Signal"
  @get external getValue: t => 'value = "value"
  @set external setValue: (t, 'value) => unit = "value"
}

module EffectStore = {
  type t
}

@module("@preact/signals-react") external createSignal: 'value => Signal.t = "signal"
@module("@preact/signals-react") external useSignal: 'value => Signal.t = "useSignal"
@module("@preact/signals-react/runtime") external useSignals: unit => EffectStore.t = "useSignals"

App.res:

external asInterval: int => intervalId = "%identity"

let counter = Preact.createSignal(0)
let intervalId = ref(asInterval(0))

@react.component
let make = () => {
  let _ = Preact.useSignals()
  let preactCount = Preact.useSignal(0)
  let (count, setCount) = React.useState(() => 0)

  let {getValue, setValue} = module(Preact.Signal)

  let startCounter = () => {
    intervalId.contents = setInterval(() => {
      counter->setValue(counter->getValue + 1)
    }, 1000)
  }

  let stopCounter = () => {
    clearInterval(intervalId.contents)
  }

  <div className="p-6">
    <h1 className="text-3xl font-semibold"> {"What is this about?"->React.string} </h1>
    <p>
      {React.string("This is a simple template for a Vite project using ReScript & Tailwind CSS.")}
    </p>
    <h2 className="text-2xl font-semibold mt-5"> {React.string("Fast Refresh Test")} </h2>
    <Button onClick={_ => setCount(count => count + 1)}>
      {React.string(`React count is ${count->Int.toString}`)}
    </Button>
    <Button onClick={_ => preactCount->setValue(preactCount->getValue + 1)}>
      {React.string(`Preact signals-react count is ${preactCount->getValue->Int.toString}`)}
    </Button>
    <p>
      {React.string("Edit ")}
      <code> {React.string("src/App.res")} </code>
      {React.string(" and save to test Fast Refresh.")}
    </p>
    <p> {React.string(`preactCount is ${preactCount->getValue}`)} </p>
    <div>
      <Button onClick={_ => startCounter()}> {React.string("Start Counter")} </Button>
      <Button onClick={_ => stopCounter()}> {React.string("Stop Counter")} </Button>
    </div>
    <p> {React.string(`The counter is ${counter->getValue}`)} </p>
  </div>
}

Again, I’m no expert on Preact, the signals vs hooks debate, Solid vs React debate, and definitely not on bindings. This is just something I wanted to do so I could play around with signals and also try to get a better understanding of creating bindings. The Preact documentation will be your best resource if you want to understand why they believe signals in React can be beneficial.

2 Likes

Nice work! I’m not up to date on signals - what’s stopping us from using it directly with Preact in ReScript? Is the “only” issue the same issues as with Solid, that the compiler can output code that breaks reactivity?

Nice job on the bindings too. One small thing you could change - currently, the actual type of the signal isn’t carried with the typings, so getValue/setValue is essentially any. By adding a type parameter to Signal.t and using that in your bindings, you’ll get the proper typings derived from the signal as it was created:

module Signal = {
  type t<'value>

  @module("@preact/signals-react") @new external make: 'value => t<'value> = "Signal"
  @get external getValue: t<'value> => 'value = "value"
  @set external setValue: (t<'value>, 'value) => unit = "value"
}

module EffectStore = {
  type t
}

@module("@preact/signals-react") external createSignal: 'value => Signal.t<'value> = "signal"
@module("@preact/signals-react") external useSignal: 'value => Signal.t<'value> = "useSignal"
@module("@preact/signals-react/runtime") external useSignals: unit => EffectStore.t = "useSignals"
2 Likes

Hey, @kay-tee I also liked the idea of signals that Preact brought, so I also did write bindings to it:

It’s also published on NpmJs: rescript-preact-signals - npm

Do you want to test it and help contributing to it?

4 Likes

One small thing you could change - currently, the actual type of the signal isn’t carried with the typings, so getValue/setValue is essentially any . By adding a type parameter to Signal.t and using that in your bindings, you’ll get the proper typings derived from the signal as it was created:

Argh, I thought it was odd that I was not getting an error when printing out the value. Your suggestion was actually part of my first attempt and somewhere along the way i removed it as I was was trying to understand how to create bindings to a Javascript/Typescript class.

Making that change triggered the error in my React.string because now getValue is carrying the type. Thanks!!

Nice, you’re a lot farther along. I haven’t played around with it yet but I have to imagine it works fine because the signatures are the same (for the stuff I do have), we just named/organized things slightly differently.

Now I see why I was having trouble with “effect”. I needed a separate binding for the effect with cleanup.

I guess if I would have googled better I would have found yours and saved myself a lot of agony. :laughing:

But the struggle good. The only way to learn how to do bindings is to get in there and get your hands dirty.

3 Likes