Skip to content

Commit 6da3fd1

Browse files
authored
fix: ensure usePreview and useProjection hooks subscribe if no ref is passed (#366)
1 parent 969d70e commit 6da3fd1

File tree

4 files changed

+138
-2
lines changed

4 files changed

+138
-2
lines changed

packages/react/src/hooks/preview/usePreview.test.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,63 @@ describe('usePreview', () => {
172172
// Restore IntersectionObserver
173173
window.IntersectionObserver = originalIntersectionObserver
174174
})
175+
176+
test('it subscribes immediately when no ref is provided', async () => {
177+
getCurrent.mockReturnValue({
178+
data: {title: 'Title', subtitle: 'Subtitle'},
179+
isPending: false,
180+
})
181+
const eventsUnsubscribe = vi.fn()
182+
subscribe.mockImplementation(() => eventsUnsubscribe)
183+
184+
function NoRefComponent(docHandle: DocumentHandle) {
185+
const {data} = usePreview(docHandle) // No ref provided
186+
return (
187+
<div>
188+
<h1>{data?.title}</h1>
189+
<p>{data?.subtitle}</p>
190+
</div>
191+
)
192+
}
193+
194+
render(
195+
<Suspense fallback={<div>Loading...</div>}>
196+
<NoRefComponent {...mockDocument} />
197+
</Suspense>,
198+
)
199+
200+
// Should subscribe immediately without waiting for intersection
201+
expect(subscribe).toHaveBeenCalled()
202+
expect(screen.getByText('Title')).toBeInTheDocument()
203+
})
204+
205+
test('it subscribes immediately when ref.current is not an HTML element', async () => {
206+
getCurrent.mockReturnValue({
207+
data: {title: 'Title', subtitle: 'Subtitle'},
208+
isPending: false,
209+
})
210+
const eventsUnsubscribe = vi.fn()
211+
subscribe.mockImplementation(() => eventsUnsubscribe)
212+
213+
function NonHtmlRefComponent(docHandle: DocumentHandle) {
214+
const ref = useRef({}) // ref.current is not an HTML element
215+
const {data} = usePreview({...docHandle, ref})
216+
return (
217+
<div>
218+
<h1>{data?.title}</h1>
219+
<p>{data?.subtitle}</p>
220+
</div>
221+
)
222+
}
223+
224+
render(
225+
<Suspense fallback={<div>Loading...</div>}>
226+
<NonHtmlRefComponent {...mockDocument} />
227+
</Suspense>,
228+
)
229+
230+
// Should subscribe immediately without waiting for intersection
231+
expect(subscribe).toHaveBeenCalled()
232+
expect(screen.getByText('Title')).toBeInTheDocument()
233+
})
175234
})

packages/react/src/hooks/preview/usePreview.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,10 @@ export function usePreview({ref, ...docHandle}: UsePreviewOptions): UsePreviewRe
7979
const subscribe = useCallback(
8080
(onStoreChanged: () => void) => {
8181
const subscription = new Observable<boolean>((observer) => {
82-
// for environments that don't have an intersection observer
82+
// For environments that don't have an intersection observer (e.g. server-side),
83+
// we pass true to always subscribe since we can't detect visibility
8384
if (typeof IntersectionObserver === 'undefined' || typeof HTMLElement === 'undefined') {
85+
observer.next(true)
8486
return
8587
}
8688

@@ -90,6 +92,10 @@ export function usePreview({ref, ...docHandle}: UsePreviewOptions): UsePreviewRe
9092
)
9193
if (ref?.current && ref.current instanceof HTMLElement) {
9294
intersectionObserver.observe(ref.current)
95+
} else {
96+
// If no ref is provided or ref.current isn't an HTML element,
97+
// pass true to always subscribe since we can't track visibility
98+
observer.next(true)
9399
}
94100
return () => intersectionObserver.disconnect()
95101
})

packages/react/src/hooks/projection/useProjection.test.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,69 @@ describe('useProjection', () => {
215215
expect(screen.getByText('Updated Title')).toBeInTheDocument()
216216
expect(screen.getByText('Added Description')).toBeInTheDocument()
217217
})
218+
219+
test('it subscribes immediately when no ref is provided', async () => {
220+
getCurrent.mockReturnValue({
221+
data: {title: 'Title', description: 'Description'},
222+
isPending: false,
223+
})
224+
const eventsUnsubscribe = vi.fn()
225+
subscribe.mockImplementation(() => eventsUnsubscribe)
226+
227+
function NoRefComponent({
228+
projection,
229+
...docHandle
230+
}: DocumentHandle & {projection: ValidProjection}) {
231+
const {data} = useProjection<ProjectionResult>({...docHandle, projection}) // No ref provided
232+
return (
233+
<div>
234+
<h1>{data.title}</h1>
235+
<p>{data.description}</p>
236+
</div>
237+
)
238+
}
239+
240+
render(
241+
<Suspense fallback={<div>Loading...</div>}>
242+
<NoRefComponent {...mockDocument} projection="{title, description}" />
243+
</Suspense>,
244+
)
245+
246+
// Should subscribe immediately without waiting for intersection
247+
expect(subscribe).toHaveBeenCalled()
248+
expect(screen.getByText('Title')).toBeInTheDocument()
249+
})
250+
251+
test('it subscribes immediately when ref.current is not an HTML element', async () => {
252+
getCurrent.mockReturnValue({
253+
data: {title: 'Title', description: 'Description'},
254+
isPending: false,
255+
})
256+
const eventsUnsubscribe = vi.fn()
257+
subscribe.mockImplementation(() => eventsUnsubscribe)
258+
259+
function NonHtmlRefComponent({
260+
projection,
261+
...docHandle
262+
}: DocumentHandle & {projection: ValidProjection}) {
263+
const ref = useRef({}) // ref.current is not an HTML element
264+
const {data} = useProjection<ProjectionResult>({...docHandle, projection, ref})
265+
return (
266+
<div>
267+
<h1>{data.title}</h1>
268+
<p>{data.description}</p>
269+
</div>
270+
)
271+
}
272+
273+
render(
274+
<Suspense fallback={<div>Loading...</div>}>
275+
<NonHtmlRefComponent {...mockDocument} projection="{title, description}" />
276+
</Suspense>,
277+
)
278+
279+
// Should subscribe immediately without waiting for intersection
280+
expect(subscribe).toHaveBeenCalled()
281+
expect(screen.getByText('Title')).toBeInTheDocument()
282+
})
218283
})

packages/react/src/hooks/projection/useProjection.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@ export function useProjection<TData extends object>({
102102
const subscribe = useCallback(
103103
(onStoreChanged: () => void) => {
104104
const subscription = new Observable<boolean>((observer) => {
105-
// for environments that don't have an intersection observer
105+
// For environments that don't have an intersection observer (e.g. server-side),
106+
// we pass true to always subscribe since we can't detect visibility
106107
if (typeof IntersectionObserver === 'undefined' || typeof HTMLElement === 'undefined') {
108+
observer.next(true)
107109
return
108110
}
109111

@@ -113,6 +115,10 @@ export function useProjection<TData extends object>({
113115
)
114116
if (ref?.current && ref.current instanceof HTMLElement) {
115117
intersectionObserver.observe(ref.current)
118+
} else {
119+
// If no ref is provided or ref.current isn't an HTML element,
120+
// pass true to always subscribe since we can't track visibility
121+
observer.next(true)
116122
}
117123
return () => intersectionObserver.disconnect()
118124
})

0 commit comments

Comments
 (0)