-
Notifications
You must be signed in to change notification settings - Fork 186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: remove extra useEffect used for resetting initial state #607
fix: remove extra useEffect used for resetting initial state #607
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 6b905af:
|
Hi @calinoracation - This looks really nice. I wasn't super happy with the solution that caused these extra renders, but it was a tradeoff for dealing with the ref issues in userland.. |
@thebuilder Thanks so much! Yeah we use the library extensively, so cutting down the re-renders is definitely a win if possible. I was even working to see if we could take it down even further and got pretty far with it down to a single // globally somewhere
type ObserveOptions = Pick<IntersectionOptions, 'root' | 'rootMargin' | 'threshold' | 'trackVisibility' | 'delay' | 'fallbackInView' | 'skip'>;
function areObserveOptionsDifferent(previous: ObserveOptions, current: ObserveOptions) {
return (
previous.root !== current.root ||
previous.rootMargin !== current.rootMargin ||
previous.trackVisibility !== current.trackVisibility ||
previous.delay !== current.delay ||
previous.fallbackInView !== current.fallbackInView ||
previous.skip !== current.skip ||
((Array.isArray(previous.threshold) ? previous.threshold.toString() : previous.threshold) !== (Array.isArray(current.threshold) ? current.threshold.toString() : current.threshold))
);
}
// inside useInView
const previousRef = React.useRef<Element | null>(null);
const unobserveRef = React.useRef<() => void>();
const previousObserveOptions = React.useRef<ObserveOptions>({});
const currentObserveOptions = {
delay,
fallbackInView,
root,
rootMargin,
skip,
threshold,
trackVisibility,
};
if (typeof window !== 'undefined' && (previousRef.current !== ref || areObserveOptionsDifferent(previousObserveOptions.current, currentObserveOptions))) {
unobserveRef.current?.();
unobserveRef.current = undefined;
previousRef.current = ref;
previousObserveOptions.current = currentObserveOptions;
if (!skip && ref) {
unobserveRef.current = observe(ref, (inView, entry) => {
setState({
inView,
entry,
});
if (callback.current) callback.current(inView, entry);
if (entry.isIntersecting && triggerOnce && unobserveRef.current) {
// If it should only trigger once, unobserve the element after it's inView
unobserveRef.current();
unobserveRef.current = undefined;
}
},
currentObserveOptions,
fallbackInView);
}
}
React.useEffect(
() => {
return () => {
if (unobserveRef.current) {
unobserveRef.current();
unobserveRef.current = undefined;
}
};
},
[],
); |
src/useInView.tsx
Outdated
}, [ref, entryTarget, triggerOnce, skip, initialInView]); | ||
const previousEntryTarget = React.useRef<Element>(); | ||
if ( | ||
typeof window !== 'undefined' && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think the window
check is needed, since you wouldn't have the entryTarget
on the server.
You could use the optionsToId method, to create a unique id for the options - That should work for detecting changes. |
I think this PR makes sense for getting rid of the If you are up for it, it would be cool with some extra eyes trying out performance improvements on the main |
@thebuilder Yeah that totally makes sense on the comment one for a new PR and more 👁️ 's. I updated this PR with the removal of the unnecessary |
🎉 This PR is included in version 9.4.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This is an attempt to reduce the amount of re-renders generated by the
useInView
hook. We can leverage the built-in batching during the render cycle to eliminate the need for this resetuseEffect
that should have a really nice impact on performance. https://beta.reactjs.org/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes goes over an explanation of this, I honestly wasn't aware of the pattern until I read it but it seemed pertinent for here.I'm not too familiar with vitest and couldn't determine a great way to test the number of re-renders in the current testing setup, but thought I'd open this to get your read on it before diving any deeper.
I see this referenced in #572 so not sure if this has some of the issues you were initially running into or not. I upgraded to the latest version and noticed we'd doubled the re-renders so thought I'd see if there was a workaround.