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
Create useInView
hook, context, and provider based on InteractionObserver/view/ref of closest element
#4120
Comments
@tofumatt this looks good to me. I made a few additions such as refactoring the One thing to keep in mind with this hook is that the |
After a discussion with @aaemnnosttv, I've moved the "has ever been onscreen" and "sticky return values after being I've upped this estimate to a 15 now since most of the logic lives here and reduced #4096's a bit. I think this is all we discussed @aaemnnosttv, so back to you to review. I'll take this issue and #4096 in the next sprint since I have a concrete idea of their approach 🙂 |
@tofumatt the IB LGTM overall, my only critique is around the name of the option
This would be
I think this may have been from a previous version as the array referenced here was state I think? |
useInView
hook, context, and provider based on InteractionObserver/view/ref of closest element
Hmm, I've tweaked the other points and I'm happy to rename that param, but I don't think "once" should be in the name of the param because it is a bit misleading 🙂 |
@tofumatt Yeah, maybe something without "once" would be more appropriate. In general, I'd prefer if the option was something we enable rather than disable. By default the value coming from the hook would be the current state (intersecting or not) which is intuitive I think; I don't really think of that as resetting when it changes. When we enable the "sticky" in-view state (maybe that would be a good name/option for it?) only then it needs to be reset, but it's only the sticky aspect of it which is being reset, not necessarily the actual in-view state as the element could still be in view in which case the context value wouldn't change 😄 Any objections to using |
QA: ❌
|
Alright, finally tracked down the cause here: anytime we use site-kit-wp/assets/js/components/wp-dashboard/WPDashboardIdeaHub.js Lines 78 to 80 in 1be7723
If we add: if ( savedIdeas === undefined ) {
return <div ref={ trackingRef }></div>;
} above those lines everything works as-expected. I'll go through and make the relevant changes to any components using |
Nice one @tofumatt. From what I can see it seems like the old |
Also I wonder if it's possible/worth writing a thin layer around |
What's especially odd is in that Idea Hub component it causes issues, but elsewhere it's fine—what seems to be the issue is if the component directly does I'm a bit puzzled; the main reason for switching dependencies was reducing the number of external libraries as the more "advanced" features of |
@techanvil I think one reason was due to |
@aaemnnosttv hmm that's interesting about the ref angle. By the look of it, the Anyway, it's useful to get the info, thanks for the detail! |
@techanvil sending it back to you for another round of QA |
QA: ✅
|
Feature Description
For #4096 to function correctly, we need a new context, provider, and hook that tracks a div/element via a ref using
InteractionObserver
and informs the hook if the element is on-screen or not.The way this should behave is that a hook (
useInView
) should get data from a<InViewContext.Provider>
provider—because React allows multiple providers for the same context and will use whichever is "closest", this approach will allow us to always provide widgets and even the app with a high-level Provider but then allow components to use a more granular approach if desired.That provider's value should be supplied by
useIntersection
.This issue should include creating a context insider every WidgetAreaRenderer, based on the intersection of
site-kit-wp/assets/js/googlesitekit/widgets/components/WidgetAreaRenderer.js
Line 123 in a1d3e7b
Do not alter or remove anything below. The following sections will be managed by moderators only.
Acceptance criteria
InViewContext
, used by the component defined below.<InViewContext.Provider>
based onInViewContext
, which should be used to provide information to theuseInView()
hook created in CreateuseInViewSelect
hook #4096.InViewContext.Provider
somewhere in the tree, so<InViewContext.Provider>
should be added to the<Root>
component, but this context should always returntrue
. This means if there is, for some reason, no lower-down contexts forInView
, the components will assume they're in view and render.useInView
inassets/js/hooks/useInView.js
.true
orfalse
—false
if the parent component/context has NEVER been on-screen, buttrue
as soon as it has been visible even once.useInView
hook should stillreturn true
.useInView()
hook'strue
status by dispatching an action in the datastore. (Likely this would be something likedispatch( CORE_UI ).resetInView()
or similar. This may mean it's best for eachuseInView
hook to store it's "viewed" status in theCORE_UI
datastore, perhaps using an instance ID for each hook. This is up to the IB to define but it should be possible to easily and globally reset theuseInView
status.)Implementation Brief
assets/js/components/InViewProvider/context.js
:const InViewContext = createContext( false );
assets/js/components/InViewProvider/index.js
: it should export theProvider
from the context above as its default export, similar to theFeaturesProvider
. (Model this entire component on theFeaturesProvider
)WidgetAreRender
component's top-level<Grid>
component (except for the hidden instance) should utilise theuseIntersection()
hook to check if the WidgetArea is on-screen (one pixel is enough):WidgetAreaRender
should have a new top-level component:<InView.Provider value={!!intersectionEntry?.intersectionRatio}>
. This context will be consumed by theuseInView
hook in CreateuseInViewSelect
hook #4096.<InView.Provider value={true}>
to theRoot
component.react-intersection-observer
from dependenciesuseInView
to useuseIntersection
triggerOnce
was used previously, use a new state variable to track this (this is currently used to prevent tracking a view event every time an element comes into view)useIntersection
as its return value might benull
.useState
state-managedhasBeenInViewOnce
value used by eachuseInView
hook. Initially it will be based onuseContext(InViewContext)
's value (true
ifref
is on-screen;false
if off-screen), and this value should update totrue
anytimeuseContext(InViewContext)
's value updates totrue
. But once the state-managed value has ever beentrue
, it should not change back tofalse
unless reset by either thegetDateRange()
changing or the "reset" action is dispatched (see below):useEffect
touseInView
that's based on the current date range (select(CORE_USER). getDateRange()
) as a dependency–whenever the date range updates, it should set the state-managedinView
value to the real current value from the I.O. entry (essentially resetting it, but without forcing a re-render if the value hasn't changed)useInView
state for any hook that has had itsuseInView
hookreturn true
at least once:resetInView()
action: Reset all hooks by incrementing the number returned byselect(CORE_UI).getValue('useInViewResetCount')
, eg by dispatchingdispatch(CORE_UI).setValues('useInViewResetCount', existingCount + 1)
useInView
with one param:sticky
. It should default tofalse
, which meansuseInView()
will always returnfalse
once theref
is scrolled off-screen. When this param is set totrue
, thehasBeenInViewOnce
local state value should be returned by theuseInView
hook instead of theuseContext(InViewContext)
value.Test Coverage
useInView
, mainly around the reset behaviour.QA Brief
useInView
(assets/js/components/wp-dashboard/WPDashboardIdeaHub.js
,assets/js/modules/idea-hub/components/dashboard/DashboardCTA.js
,assets/js/modules/idea-hub/components/dashboard/DashboardIdeasWidget/index.js
, andassets/js/modules/pagespeed-insights/components/dashboard/DashboardPageSpeed.js
) still function as expected using the newuseIntersection
hook.Changelog entry
useInView
hook.The text was updated successfully, but these errors were encountered: