Skip to content

Commit

Permalink
feat(react): improve ClientOnly by useSyncExternalStore
Browse files Browse the repository at this point in the history
Co-authored-by: SeongMin Kim <86355699+Collection50@users.noreply.github.com>
  • Loading branch information
manudeli and Collection50 committed Jul 18, 2024
1 parent 4b17387 commit ecfd946
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/cyan-tigers-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@suspensive/react": minor
---

feat(react): improve ClientOnly by useSyncExternalStore
2 changes: 1 addition & 1 deletion packages/react/src/components/ClientOnly.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactNode } from 'react'
import { useIsClient } from '../hooks/useIsClient'
import { useIsClient } from '../hooks'

interface ClientOnlyProps {
children: ReactNode
Expand Down
70 changes: 64 additions & 6 deletions packages/react/src/hooks/useIsClient.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,70 @@
import { renderHook } from '@testing-library/react'
import { useIsClient } from '.'
import { useState, useSyncExternalStore } from 'react'
import { noop } from '../utils'
import { useIsClient, useIsomorphicLayoutEffect } from '.'

describe('useIsClient', () => {
it('should return true when client side painting start', () => {
const {
result: { current: isClient },
} = renderHook(() => useIsClient())
it('should return true in client side rendering', () => {
const returnedFirst = renderHook(() => useIsClient())
expect(returnedFirst.result.current).toBe(true)
returnedFirst.unmount()
const returnedSecond = renderHook(() => useIsClient())
expect(returnedSecond.result.current).toBe(true)
})
it("'s comparison with legacy useIsClientOnly", () => {
// check CSR environment first
expect(typeof document !== 'undefined').toBe(true)

let renderCount = 0
let chanceIsClientToBeFalse = false

const emptySubscribe = () => noop
const getSnapshot = () => true
const getServerSnapshot = () => false
const useIsClient = () => {
const isClient = useSyncExternalStore(emptySubscribe, getSnapshot, getServerSnapshot)
renderCount++
if (!isClient) {
chanceIsClientToBeFalse = true
}
return isClient
}
const { unmount } = renderHook(() => useIsClient())
expect(renderCount).toBe(1)
expect(chanceIsClientToBeFalse).toBe(false)
unmount()
renderHook(() => useIsClient())
expect(renderCount).toBe(2)
expect(chanceIsClientToBeFalse).toBe(false)
})
it('improve legacy useIsClientOnly', () => {
// check CSR environment first
expect(typeof document !== 'undefined').toBe(true)

let renderCount = 0
let chanceIsClientToBeFalse = false

/**
* @deprecated This is legacy useIsClientOnly
*/
const useIsClientLegacy = () => {
renderCount++
const [isClient, setIsClient] = useState(false)
if (!isClient) {
chanceIsClientToBeFalse = true
}
useIsomorphicLayoutEffect(() => {
setIsClient(true)
}, [])

expect(isClient).toBe(true)
return isClient
}
const { unmount } = renderHook(() => useIsClientLegacy())
expect(renderCount).toBe(2)
expect(chanceIsClientToBeFalse).toBe(true)
unmount()
renderHook(() => useIsClientLegacy())
expect(renderCount).toBe(4)
expect(chanceIsClientToBeFalse).toBe(true)
})
})
17 changes: 6 additions & 11 deletions packages/react/src/hooks/useIsClient.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { useState } from 'react'
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'
import { useSyncExternalStore } from 'react'
import { noop } from '../utils'

export const useIsClient = () => {
const [isClient, setIsClient] = useState(false)

useIsomorphicLayoutEffect(() => {
setIsClient(true)
}, [])

return isClient
}
const emptySubscribe = () => noop
const getSnapshot = () => true
const getServerSnapshot = () => false
export const useIsClient = () => useSyncExternalStore(emptySubscribe, getSnapshot, getServerSnapshot)

0 comments on commit ecfd946

Please sign in to comment.