shadaj/slinky

Issue with memo

mvillafuertem opened this issue · 5 comments

Hi all, I am experiencing component refresh even though I am using React.memo. Am I wrong about something?

Here my use case, see Memorize and open console https://mvillafuertem.github.io/zio-react/hooks/

@react object MemorizeApp {

  type Props = Unit

  val component: FunctionalComponent[Props] = FunctionalComponent[Props] { _ =>
    val (counter, increment, _, _) = useCounter(10)
    val (show, setShow)            = useState(true)

    val handleClick: js.Function1[SyntheticMouseEvent[TagElement#RefType], Unit] =
      (_: SyntheticMouseEvent[TagElement#RefType]) => setShow(!show)

    Fragment(
      h1("Memorize"),
      h3(Small(counter)),
      button(className := "btn btn-success", onClick := increment)("+1"),
      button(className := "btn btn-outline-success ml-3", onClick := handleClick)(s"Show/Hide ${js.JSON.stringify(show)}")
    )

  }

}


@react object Small {

  case class Props(value: Int)

  val component: FunctionalComponent[Props] = React.memo(FunctionalComponent[Props] {
    case Props(value) =>
      println("Refresh Component")
      small(value)
  })

}

Hi @ramnivas, maybe you know why this happens. Thanks in advice!

By default React performs shallow compare of the props (and for Option props, shallow comparison result in not-equal). So try passing the second argument to it (something like (oldProps: Props, newProps: Props) => oldProps == newProps)

@ramnivas something strange is happening to me, the same js code works without use comparison. it's something special from slinky?

https://stackblitz.com/edit/react-sl3mi8

import React from "react";
import "./style.css";

//import { useState } from 'react';
const { useState } = React;

export const Small = React.memo(({value}) => {
  console.log("Refresh Component")
  return (
    <small>{ value }</small>
  )
})

export const MemorizeApp = () => {
  const [count, setCount] = useState(0)
  const [show, setShow] = useState(true)

  return (
    <div>
      <h1><Small value={ count } /></h1>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
       <button onClick={() => setShow(!show)}>
        Show {JSON.stringify(show)}
      </button>
    </div>
  )
}



export default function App() {
  return (<MemorizeApp />
  );
}

How would it be solved in this use case?

Here my real use case, see Father and open console https://mvillafuertem.github.io/zio-react/hooks/

@react object CallbackHookApp {

  type Props = Unit

  val component: FunctionalComponent[Props] = FunctionalComponent[Props] { _ =>
    val (counter, setCounter) = useState[Int](0)


    val incrementCallback: () => Unit =
      useCallback(() => setCounter(counter => counter + 1), Seq(setCounter))

    Fragment(
      h1("CallbackHookApp"),
      h3(counter),
      ShowIncrement(incrementCallback)
    )
  }

}


@react object ShowIncrement {

  case class Props(increment: () => Unit)

  val component: FunctionalComponent[Props] = React.memo(
    FunctionalComponent[Props] {
      case Props(increment) =>
        println("Refresh Component, only show when the props changes")
        button(className := "btn btn-success", onClick := increment)("+1")

    },
    (oldProps: Props, newProps: Props) => oldProps.increment eq newProps.increment
  )

}
evbo commented

this is such a great reference example on SyntheticMouseEvent as well as React.memo, I keep coming back to it!

@mvillafuertem call me lazy, but I usually solve issues like this with a surrogate refresh_id that dictates when the component should re-render, rather than relying on equality checks of something like functions.

So essentially add a prop called refresh_id or something and increment it by 1 every time you want to actually change increment, then in your React.memo use the surrogate id as the check.

There might be a way to compare functions in ScalaJS, but... laziness :)