diff --git a/package.json b/package.json
index b7a3f901..a7f4c0b7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-storefront",
- "version": "8.4.0",
+ "version": "8.5.0",
"description": "Build and deploy e-commerce progressive web apps (PWAs) in record time.",
"module": "./index.js",
"license": "Apache-2.0",
diff --git a/src/LazyHydrate.js b/src/LazyHydrate.js
index 2e744582..8ee8047a 100644
--- a/src/LazyHydrate.js
+++ b/src/LazyHydrate.js
@@ -1,5 +1,6 @@
import React, { useEffect, useState, useRef } from 'react'
import PropTypes from 'prop-types'
+import useIntersectionObserver from './hooks/useIntersectionObserver'
import { StylesProvider, createGenerateClassName } from '@material-ui/core/styles'
import { SheetsRegistry } from 'jss'
@@ -52,18 +53,6 @@ const isBrowser = () => {
)
}
-// Used for detecting when the wrapped component becomes visible
-const io =
- isBrowser() && IntersectionObserver
- ? new IntersectionObserver(entries => {
- entries.forEach(entry => {
- if (entry.isIntersecting || entry.intersectionRatio > 0) {
- entry.target.dispatchEvent(new CustomEvent('visible'))
- }
- })
- })
- : null
-
function LazyHydrateInstance({ className, ssrOnly, children, on, index, ...props }) {
function isHydrated() {
if (isBrowser()) {
@@ -77,40 +66,52 @@ function LazyHydrateInstance({ className, ssrOnly, children, on, index, ...props
const childRef = useRef(null)
const [hydrated, setHydrated] = useState(isHydrated())
+ function hydrate() {
+ setHydrated(true)
+ // Remove the server side generated stylesheet
+ const stylesheet = window.document.getElementById(`jss-lazy-${index}`)
+ if (stylesheet) {
+ stylesheet.remove()
+ }
+ }
+
useEffect(() => {
setHydrated(isHydrated())
}, [props.hydrated, ssrOnly])
+ if (on === 'visible') {
+ useIntersectionObserver(
+ // As root node does not have any box model, it cannot intersect.
+ () => childRef.current.children[0],
+ (visible, disconnect) => {
+ if (visible) {
+ hydrate()
+ disconnect()
+ }
+ },
+ [],
+ // Fallback to eager hydration
+ () => {
+ hydrate()
+ },
+ )
+ }
+
useEffect(() => {
if (hydrated) return
- function hydrate() {
- setHydrated(true)
- // Remove the server side generated stylesheet
- const stylesheet = window.document.getElementById(`jss-lazy-${index}`)
- if (stylesheet) {
- stylesheet.remove()
- }
- }
-
- let el
- if (on === 'visible') {
- if (io && childRef.current.childElementCount) {
- // As root node does not have any box model, it cannot intersect.
- el = childRef.current.children[0]
- io.observe(el)
- }
+ if (on === 'click') {
+ childRef.current.addEventListener('click', hydrate, {
+ once: true,
+ capture: true,
+ passive: true,
+ })
}
- childRef.current.addEventListener(on, hydrate, {
- once: true,
- capture: true,
- passive: true,
- })
-
return () => {
- if (el) io.unobserve(el)
- childRef.current.removeEventListener(on, hydrate)
+ if (on === 'click') {
+ childRef.current.removeEventListener('click', hydrate)
+ }
}
}, [hydrated, on])
diff --git a/src/hooks/useIntersectionObserver.js b/src/hooks/useIntersectionObserver.js
index adae7866..ab9409d7 100644
--- a/src/hooks/useIntersectionObserver.js
+++ b/src/hooks/useIntersectionObserver.js
@@ -1,4 +1,11 @@
-import React, { useEffect } from 'react'
+import { useEffect } from 'react'
+
+function getElement(ref) {
+ if (ref && ref.current) {
+ return ref.current
+ }
+ return ref
+}
/**
* Calls a provided callback when the provided element moves into or out of the viewport.
@@ -25,21 +32,25 @@ import React, { useEffect } from 'react'
*
* ```
*
- * @param {Function} getRef A function that returns a ref pointing to the element to observe
+ * @param {Function} getRef A function that returns a ref pointing to the element to observe OR the element itself
* @param {Function} cb A callback to call when visibility changes
* @param {Object[]} deps The IntersectionObserver will be updated to observe a new ref whenever any of these change
+ * @param {Function} notSupportedCallback Callback fired when IntersectionObserver is not supported
*/
-export default function useIntersectionObserver(getRef, cb, deps) {
+export default function useIntersectionObserver(getRef, cb, deps, notSupportedCallback) {
useEffect(() => {
+ if (!window.IntersectionObserver) {
+ notSupportedCallback &&
+ notSupportedCallback(new Error('IntersectionObserver is not available'))
+ return
+ }
const observer = new IntersectionObserver(entries => {
// if intersectionRatio is 0, the element is out of view and we do not need to do anything.
cb(entries[0].intersectionRatio > 0, () => observer.disconnect())
})
-
- const ref = getRef()
-
- if (ref && ref.current) {
- observer.observe(ref.current)
+ const el = getElement(getRef())
+ if (el) {
+ observer.observe(el)
return () => observer.disconnect()
}
}, deps)
diff --git a/test/hooks/useIntersectionObserver.test.js b/test/hooks/useIntersectionObserver.test.js
index a02533fe..8adf7285 100644
--- a/test/hooks/useIntersectionObserver.test.js
+++ b/test/hooks/useIntersectionObserver.test.js
@@ -45,4 +45,32 @@ describe('useIntersectionObserver', () => {
mount(