Reputation: 79278
Basically this:
function MyComponent() {
let [count, setCount] = useState(1)
let memo = useMyMemo(() => new MyClass)
return <div onClick={update}>{count}</div>
function update() {
setCount(count + 1)
}
}
function useMyMemo(fn) {
// what to do here?
}
class MyClass {
}
I would like for useMyMemo
to only return 1 instance of the class per component instance. How do I set this up to implement it without resorting to using any of the existing React hooks? If it's not possible without the React hooks, they why not? If it is possible only through accessing internal APIs, how would you do that?
As a bonus it would be helpful to know how it could be passed property dependencies, and how it would use that to figure out if the memo should be invalidated.
Upvotes: 6
Views: 5300
Reputation: 4259
useMemo
using useRef
.Note: You can shallowCompare or deepCompare to compute
isChanged
based on your need. Example if dependencies areobjects
.
/* File: useMemo.js */
import { useRef } from "react";
export function useMemo(callback, deps) {
const mPointer = useRef([deps]); // Memory pointer (Make sure to read useRef docs)
const isChanged = mPointer.current[0].some(
// If dependencies changed
(item, index) => item !== deps[index]
);
if (mPointer.current.length === 1 || isChanged) {
// If first time or changed, compute and store it
mPointer.current = [deps, callback()];
}
return mPointer.current[1];
}
See CodeSandbox demo.
Read: useRef returns a mutable ref object whose
.current
property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
closure
- [ For educational purpose ONLY! ]/* File: useMemo.js */
function memo() {
const stack = {};
return (callback, deps) => {
/* Creating hashkey with dependencies + function source without whitespaces */
const hash = deps.join("/") + "/" + callback.toString().replace(/\s+/g, "");
if (!stack[hash]) {
stack[hash] = callback();
}
return stack[hash];
};
};
export const useMemo = memo();
See CodeSandbox demo.
Idea explaination : With above code trying to create a unique
hash
by combining dependencies value & function source usingtoString
, removing spaces in string to make hash as unique as possible. Using this as a key in thehashmap
to store the data and retrieve when called. The above code is buggy, for example callback result that are cached are not removed when component unmount's. AddinguseEffect
and return clean-up function should fix this during unmount. Then this solution becomes tightly coupled with react framework
Hope this helps. Feedback is welcomed to improve the answer!
Upvotes: 5
Reputation: 260
I think you're describing how useMemo
already works, assuming you pass []
as the dependencies parameter. It should already create one instance of MyClass
per instance of MyComponent
. However, the documentation says that future versions of useMemo
will not guarantee that the value is only called once, so you might want to try useRef
.
const memo = useRef(null);
if (!memo.current) {
memo.current = new MyClass()
}
If you want it to create a new instance of MyClass
when dependencies change, you'll have to either use useMemo
(and accept the chance that the value might be invalidated occasionally), or do your own shallow comparison against the previous value. Something like this:
const useMyMemo = (create, dependencies) => {
const val = React.useRef(create());
const prevDependencies = React.useRef([]);
if (!shallowEquals(dependencies, prevDependencies.current)) {
val.current = create();
prevDependencies.current = [...dependencies];
}
return val;
};
I'll leave the implementation of shallowEquals
to you, but I believe lodash has one.
Now you really asked for a function that doesn't use any React hooks, and mine uses useRef
. If you don't want useRef
, you can create your own simple memoization function that always returns the same pointer regardless of changes to .current
.
Upvotes: 6