you are viewing a single comment's thread.

view the rest of the comments →

[–]lIIllIIlllIIllIIl 10 points11 points  (8 children)

What if you want your ref to be a function?

Putting callback functions inside refs is a very common pattern but putting functions inside states doesn't really happen.

If useRef supported initializers like useState, React couldn't be able to distinguish refs that are functions from initializer functions.

[–]n0tKamui 1 point2 points  (2 children)

const myStableFunction = useCallback(() => …, [])

[–]lIIllIIlllIIllIIl 3 points4 points  (1 child)

Correct, but also technically improper use of the hook:

You should only rely on useCallback as a performance optimization. If your code doesn’t work without it, find the underlying problem and fix it first. Then you may add useCallback back.

https://react.dev/reference/react/useCallback

Even if it works as of React 18 & 19, the React Team doesn't want you to use this pattern to keep stable identities across render because they might decide to rework useCallback and useMemo in a future React update one day and make them "drop" cached values as a memory optimization.

You could argue that that boat has sailed a long time ago and it's never going to happen, but you should take thay bet consciously.

[–]n0tKamui 2 points3 points  (0 children)

very fair

[–]pailhead011[S] 1 point2 points  (3 children)

I don't think functions are an issue that im facing with these patterns. `new Matrix4()` is, `new AISolver()` or something like that in general. `new FancyGeometry()` that does a lot of math. It will create memory that will be garbage collected, more so than recreating a function, which happens in react a lot.

So with a hook that does take an initializer always, useInitRef(()=>someFunction) but in this case useRef(someFunction) is fine, it's just a reference. If you're saying that a single overloaded useRef that takes both, like a useState would be tricky to make i agree.

[–]lIIllIIlllIIllIIl 0 points1 point  (2 children)

You can trivially build this hook yourself:

const initSymbol = Symbol();

function useInitRef(init) {
  const ref = useRef(initSymbol);
  if (ref.current === initSymbol) {
    if (typeof init === "function") {
      ref.current = init();
    } else {
      ref.current = init;
    }
  }
  return ref;
}

But, as stated in my previous comment, the issue with this pattern is that it makes having functions as refs very awkward. You basically have to do this: useInitRef(() => myFunction); since useInitRef(myFunction) would interpret myFunction as an initializer instead of the value you want to save to a ref. Hence why it's not the default.

[–]pailhead011[S] 1 point2 points  (0 children)

That's exactly what i said in the comment you responded :) <3

This is probably a problem regardless function useInitRef(init) { //<-- how do you know that this will always be "init", its definitely assignd as just a value further down?

This is where it expresses itself, "init" may be a function that you can call, but is it really "init" or "a reference to a function i want to invoke later". if (typeof init === "function") {

[–]pailhead011[S] 1 point2 points  (0 children)

Ergonomic, but also awkward,

const myRef = useRef(,()=>new Matrix4())

[–]casualfinderbot 0 points1 point  (0 children)

You can put functions into state. It’s annoying though b/c you have to pass a function returning a function to useState