Is it safe to use such a pattern:
const appLoaders = useMemo(() => React.createRef(), [])
The thing is I use this ref in useEffect and it is needed in the dependency array (exhaustive-deps). The above pattern does the trick and everything seems to work - without the memo, when I put the ref in the dependency array the app was in constant rerender.
I am just wondering if there are some 'traps' that will surprise me in certain circumstances.
Don't see any issue with your version, but
const appLoaders = useRef();
looks much shorter and does exactly the same(referential equality across re-renders, we set initial value, changing value does not cause re-render).
PS Actually useRef is not a replacement for React.createRef(they have different purpose but similar names, sometimes people misunderstand/misuse), so don't be confused by similarity.
But in this particular case they are definitely interchangeable
Related
I wrote a Hook to return an object. Something like
function useFoo() {
const stuff = useStuff()
const id = useId()
const foo = {
stuff,
id,
}
return foo
}
I know that I can wrap foo inside useMemo to make its reference stable between re-renders so it can be safely included in dependency lists (by safely I meant it doesn't bust memorizations between re-renders).
But I don't like the costs of putting useMemo on things. The costs involve:
allocating the dependency list in memory
shallow-compare the dependency list (a o(n) process)
I have make sure anything used/referenced from that object also are memo'ed so as a result I have use more useMemo across the codebase
And at this point we can work around this issue by either:
make foo the state of the Hook. i.e.
function useFoo() {
const stuff = useStuff()
const id = useId()
const [foo, setFoo] = {
stuff,
id,
}
return foo // now we don't need to memorize it
}
But I guess this is unnecessary for various reasons but I cannot pinpoint exactly what the tradeoffs are for now.
putting it inside a ref, i.e. the Latest Ref Pattern
The two workarounds are based on the premise that we obey the react-hooks/exhaustive-deps eslint rule, which is that we should include foo in dependency lists to avoid stale closure issues. For example, when foo is used inside useEffect, we need to include it in the dependency list to keep things in sync.
However I keep wondering if there are legit cases where we know for sure that we can safely omit foo from any dependency lists, i.e. ignoring react-hooks/exhaustive-deps eslint rule (I know it is often discouraged).
For example, foo never changes during the lifetime of useFoo. Then in which case, it doesn't make any difference whether or not we include foo in a dependency list. Consequently, we don't even bother memorizing it in the first place in useFoo.
So my question is, are there any patterns or rules of thumbs that we can leverage to confidently not memorize foo as it doesn't matter whether or not we include it in any dependency lists?
So, I've got this titleRef
const titleRef = useRef<HTMLHeadingElement | null>(null);
It's attached to this header:
<h1 ref={titleRef}>Hi!</h1>
I want to use it to do some work with styling on screen resize, like so:
if (titleRef.current.style.offsetWidth > '10px') {...}
TS doesn't like this. See, useRef(null) means that titleRef could equal null (which, fair enough) and so everywhere I use titleRef, I have to account for that with something like
if (titleRef.current) {
...do the thing
}`
which gets to be a pain in the ass if titleRef gets used a lot.
Is there more elegant way to handle this situation? Directly set titleRef to be the <h1> element directly in useRef? Or, instead of using null, pass useRef() some "generic" HTMLHeadingElement?
Edit: Optional chaining was the the first thing I tried, but, that doesn't appear to help:
Edit 2: adding an ! does clear the warning, but it also makes the value static and might lead to trouble, depending on your use case.
Edit 3: using optional chaining can also make the linter mad, if you're going modify titleRef.current.
If you're not modifying the value, only reading it, then ? should be fine, but if not...
TL;DR
While the answers below work some of the time, the downsides don't seem worth it, to me. For now, I'm going to stick to my if spam...
OK, I ended up testing this. Optional chaining can help you get rid of the if that's bloating your code:
const myTestRef = React.useRef<IFancyRef>(null);
const handleClick = () => {
myTestRef.current?.focus();
// or
myTestRef.current!.focus(); // this assumes you KNOW that myTestRef is assigned
};
Here Codesandbox for you
If you are not going to assign null in the future and it is just an initial value, then you can use :
const titleRef = useRef<HTMLHeadingElement>(null!);
The exclamation mark after null tells typescript that it is meant to be initial value and the value will most possibly change.
Check out Typescript-cheatsheets notes here
I hope someone can help me. I tried to find the answer to my question but I couldn't, so if it is there and I couldn't find it, I apologize in advance.
So, I have an expensive operation that depends on 3 objects stored in the redux store. Since it is expensive, I want to execute it only when any of those 3 objects change.
To avoid making the function executed by useMemo too complex I split it in smaller functions that are then call when needed, something like this:
const computedValue = useMemo(() => {
...
const result = processStoreObject1(storeObject1)
...
}, [storeObject1, storeObject2, storeObject3, processStoreObject1])
Now, I do not want to list processStoreObject1 as a dependency of useMemo, the computed value does not depend on it, the computed value only depend on the 3 store object. However, if I do not list the function as a dependency of useMemo I get this lint warning:
"React Hook useMemo has a missing dependency: 'processStoreObject1'. Either include it or remove the dependency array. eslint(react-hooks/exhaustive-deps)"
Because of this warning I have to include the function in the dependencies array, and because the function is declared inside the component, similar to this:
const MyComponent = () => {
...
const processStoreObject1 = () => {
// Do something
}
...
}
I have to wrap it in a useCallback, otherwise it changes with each render and the useMemo is recalculated all the time (which is wrong). The warning that I get if I do not wrap the processStoreObject1 with useCallback is this:
"The 'processStoreObject1' function makes the dependencies of useMemo Hook (at line NNN) change on every render. To fix this, wrap the 'processStoreObject1' definition into its own useCallback() Hook.eslint(react-hooks/exhaustive-deps)"
I know one easy solution is to define processStoreObject1 outside the component so it does not get created in each render but I do not like that way, the function is only consumed inside the component so I want to have the definition in there.
To summarize, the question is, how can I use a function inside the function executed by useMemo without adding the dependency to the dependencies array. I know it is doable, I saw some examples of functions being used without being in the dependencies array.
I will be thankful if anyone can help me.
Thanks !
Assume you are using React and you are writing a custom hook useSomething that returns the identical same thing each time it is invoked for the same component.
const something = useSomething()
// useSomething() at time X === useSomething() at time Y
If you now use this something value inside of a useEffect(() => ...) and you do not pass something as a dependency to the array of the second argument of useEffect then the linter will warn you:
React Hook useEffect has a missing dependency: 'something'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)
Of course ESLint cannot know that something will always stay identical (per component), but adding not-changing things like something to the dependency array of useEffect each time they are used is really annoying. Just deactivating react-hooks/exhaustive-deps does also not seem to be a good solution (nor using // eslint-disable-next-line react-hooks/exhaustive-deps).
Is there a better solution than to add things like that unnecessarily to the dependency array of useEffect just to make the Linter happy?
Please find a simple demo here:
https://codesandbox.io/s/sad-kowalevski-yfxcn [Edit: Please be aware that the problem is about the general pattern described above and not about this stupid little demo - the purpose of this demo is just to show the ESLint warning, nothing else]
[Edit] Please find an additional demo here:
https://codesandbox.io/s/vibrant-tree-0cyn1
Here
https://github.com/facebook/react/issues/14920#issuecomment-471070149
for example you can read this:
If it truly is constant then specifying it in deps doesn't hurt. Such as the case where a setState function inside a custom Hook gets returned to your component, and then you call it from an effect. The lint rule isn't smart enough to understand indirection like this. But on the other hand, anyone can wrap that callback later before returning, and possibly reference another prop or state inside it. Then it won’t be constant! And if you fail to handle those changes, you’ll have nasty stale prop/state bugs. So specifying it is a better default.
So maybe just adding that never-changing values to the dependency array of useEffect may yet be the best solution. Nevertheless I hoped there would be something like a ESLint react-hooks configuration possibility to define a list of hook names which whose return values should be considered as static.
The example is a little contrived but I suspect you may wish to create a new useEffect block without this dependency.
If the store is not changing though I'd question why you'd wish to console log it time. If you wish to log it only on change then you'd add someStore to your dependency array. It really depends on what you're trying to achieve and your seperation of concerns.
I'd argue that if someStore is used as part of whatever logic is handled in this effect then it does belong in your dependency array.
You could also alternatively move const something = useSomething() into your effect and extract it as a custom hook link
useEffect(() => {
console.log("Current state (may change)", someState);
}, [someState]);
useEffect(() => {
console.log("Current store (will never change)", someStore);
});
Here's some sample code I've written that works fine:
useEffect(() => {
if (!rolesIsLoading && rolesStatus === 200) {
customer.roles = rolesData.roles;
}
}, [rolesIsLoading, rolesStatus]);
I'm getting this warning in the console:
React Hook useEffect has missing dependencies: 'customer.roles' and 'rolesData.roles'. Either include them or remove the dependency array react-hooks/exhaustive-deps
The thing is, the code works fine now and, in some cases, when I add such dependencies, as instructed, I end up with an endless loop or some other problem.
I'd appreciate any advice you might have as to how you resolved this when you've encountered a similar situation.
From React DOCS:
Conditionally firing an effect
The default behavior for effects is to fire the effect after every completed render. That way an effect is always recreated if one of its dependencies changes.
However, this may be overkill in some cases, like the subscription example from the previous section. We don’t need to create a new subscription on every update, only if the source props has changed.
To implement this, pass a second argument to useEffect that is the array of values that the effect depends on. Our updated example now looks like this:
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
So, from the exercpt above we see that the dependencies array serves the purpose to conditionally fire an effect.
That warning you're getting is because you're using some external (from the effect's perspective) variables in your effect function that you're not mentioning in the dependency array.
React is worried that you might be getting new values for those variables in future renders, and since your effect uses them, React "default intent" is to re-run that effect with the new variables values. So your effect would always be up to date.
So you need to think if you want to re-run your effect if/when those variables change in the future.
Basic recommendations:
Add them to the dependency array
If they never change, there will be no difference
If they change, add some conditionals to your effect to decide whether you should do something based on the new variables values or not
If they're changing only in reference, but not in value (like functions, arrays, objects, etc), you can use the useCallback or useRef hook to preserve the "reference" for those variables from the first render and not get new references on every render.