planttheidea/moize

React hooks for useMoize

Closed this issue · 10 comments

It would be excellent if moize had an alternative to react hooks useMemo().

I've been thinking about this myself, I might give it a stab this weekend.

It will likely end up being based on the API of useCallback instead of useMemo. Same result, but easier to abstract.

So this was actually fairly easy to implement. The planned API:

function App({first, second, object}) {
  // standard usage
  const sum = useMoize((a, b) => a + b, [first, second]);
  // with options
  const deepSum = useMoize((obj) => obj.a + obj.b, [object], {isDeepEqual: true});

  return `Sum of ${first} and ${second} is ${sum}. Sum of ${object.a} and ${object.b} is ${deepSum}.`;
}

What do you think?

NOTE: I should call out that this is not an exact 1-to-1 with useCallback or useMemo, because the array passed as the second param would actually be used as args to the call to the moized function. In both of the aforementioned built-in hooks, they are considered "dependencies", where the shallow-equal comparison of them determines whether to call the function or not. This very simple use-case doesn't need moize (there would be no gain), but there are scenarios where useMoize would be useful ... creating an instance-specific memoized function, automated transformation of args with the memoized computation, etc.

Wow., this looks great.

I am already always using the full dependency list anyways, and eslint-plugin-react-hooks enforces it, so it seems okay for the second param to work as args.

If you have an alpha I'd love to test it out.

By adding only a few hooks to your code, this could be a great help with performance.

Hmm ... so a bit of a problem from a maintenance perspective; currently moize does not actually require that we import React, however to create this hook we need to. This creates a breaking change because of the additional dependency (not required before) and the version of that dependency (before you could use it with React as low as 0.14.0). I'll need to think about this, because I don't like those ramifications.

What I might do instead is show how to build the hook in the README, because it is very simple to write and add to any project:

function useMoize(fn, nextArgs, options) {
  const moizedFn = useRef(moize(fn, options));

  return moizedFn.current(...nextArgs);
}

Do you think that would be reasonable?

NP. It could be added later when dependencies increase. The important part is that it's tested and works well. I am putting this to use right now. Thank you.

Sure thing, I can add some tests for a "hook example".

@Yzrsah just added the documentation + test in the referenced PR, let me know if this is enough for what you need.

Closing after the merge of #107, see the documentation for details.

@planttheidea Thanks for the updates. I've had a chance to test this further

It is not using the cache because component instances each use their own ref.

For example, if there's useMoize((x) => expensiveThing(x), [arg]) and 50 components all render using the same value for [arg], we will still get expensiveThing() created 50 times separately because the useRef is to different instances, so the cache is not used.

It would be great if there was a way to utilize the cache across component instances to get the effect of predictability in stateless pure components - if the [arg] has not changed, it should be the same result

I'm trying to see if this can be done without using a Context provider

Well... Yeah I thought your goal was to create instance specific memoization. If you want memoization across all instances... Why not create just a standard memoized function?