slinky
slinky copied to clipboard
Issue with memo
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
)
}
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 :)