rescript-lang/rescript-react

Use ReScript's equality (`==`) instead of `Object.is` to determine if component state or props are changed?

Minnozz opened this issue · 5 comments

When using an option, record or other more complex type as a prop or with React.useState, the component is often unnecessarliy re-rendered because React uses Object.is to compare the previous and new values. I would expect ReScript's == (which compiles to Caml_obj.caml_equal) to be used, which would prevent lots of re-renders.

I think this would contradict a lot of people's mental model of how React works, given ReScript's audience of JS users who will have already used React there. It also goes against one of the design goals of these bindings being "zero cost".

@tom-sherman I think you're right that that would be unexpected behavior.

It would be nice to be able to opt-in to this behavior though, without having to manually write custom comparison boilerplate. Do you know of a way to do that?

I don't I'm afraid.

I'd probably look at polyfilling the record & tuple proposal instead. I've been experimenting with that a bit here: https://github.com/tom-sherman/rescript-record-tuple

Maybe this could be an option passed to the decorator: @react.component(~compare=(==))

You can do this in user-land - feel free to copy and publish if you're interested, but since it has a runtime and is outside of React scope it won't be put up here. The ppx is already too complex to manage, so as fun as it would be and as clean as it might be, it (unfortunately) definitely won't be added there.

Not guaranteeing this works as I've not tested at all, but something like:

let useCustomUpdater = (~compare=\"=", next) => {
  let prevRef = React.useRef(next)
  let return = React.useRef(true)
  let prev = prevRef.current
  if !compare(prev, next) {
    return.current = !return.current
  }
  prevRef.current = next
  [return.current]
}

// usage
useEffect(() => None, useCustomUpdater(propA))

Also please check perf before doing this! Rerenders for small components can be very fast, Object.is is very fast, Caml_obj.caml_equal is not always very fast. You should definitely not blindly replace all your hooks deps with this.