shadaj / slinky

Write Scala.js React apps just like you would in ES6
https://slinky.dev
MIT License
654 stars 57 forks source link

React Hooks support useCallback and useMemo arguments #286

Closed evbo closed 5 years ago

evbo commented 5 years ago

I've been using Slinky for quite sometime, all the while thinking that per the react.js docs useCallback and useMemo should strictly take () => Unit and () => T as their first argument, respectively.

But towards the bottom of this issue is a valid use-case for when a variable can't easily or conveniently be controlled in state, but must be referenced in the callback that you also might want memoized to avoid re-rendering the child you're passing it to.

Sure enough, if you play around with this code pen, you can write a valid useCallback or useMemo that takes other types, e.g.: useCallback[String](...)

React.withHooks(() => {
  const inputState = React.useState('Hello, world!');
  const onButtonClick = React.useCallback(
    (props) => (
      (e) => {
        const [input] = inputState;
        alert(e);
      }
    ),
    (props) => [inputState[0]]
  );
  return (props) => {
    const [input, setInput] = inputState;
    return (
      <>
        <input value={input} onChange={(e) => setInput(e.currentTarget.value)} />
        <button onClick={onButtonClick('data I need to send not controlled in state')}>Alert</button>
      </>
    );
  };
})

Can Slinky hooks support the following instead? In these cases, the extra type is for an argument that isn't controlled in any state that would otherwise be watched as a dependency:

@inline def useCallback[T](callback: T => Unit, watchedObjects: Iterable[Any]): T => Unit = {
    ...
}

@inline def useMemo[A,T](memoValue: A => T, watchedObjects: Iterable[Any]): T = {
    ...
}

and would get used like:

val someCallback = useCallback[String](
  (dataINeedNotStoreInState: String) => { println(dataINeedNotStoreInState + stateData) },
  watchedObjects = Seq(stateData)
)

Note that there is a work around, albeit forcing the reducer design pattern, which isn't always preferred by matter of coding convention:

So instead of trying to implement:

val func = useCallback[String]((e: String) => {setState(someState + e)} )

You can just do the following for now:

val (someState, func) = useReducer[String, String](
    reducer = (current: String, newChange: String) => {current + newChange}, 
    initialState = ""
)
shadaj commented 5 years ago

In the case of useMemo, Slinky doesn't actually need a new API since A => T can just be the type that the existing T is inferred to be. I'll take a look at useCallback, that's not something that Slinky handles right now.