Skip to content

Commit

Permalink
feat(react): improve ClientOnly by useSyncExternalStore (#1105)
Browse files Browse the repository at this point in the history
Idead by @TkDodo 🙇

close #528 

# Overview

<!--
    A clear and concise description of what this pr is about.
 -->

## PR Checklist

- [x] I did below actions if need

1. I read the [Contributing
Guide](https://github.com/toss/suspensive/blob/main/CONTRIBUTING.md)
2. I added documents and tests.

---------

Co-authored-by: Dominik Dorfmeister <1021430+TkDodo@users.noreply.github.com>
Co-authored-by: SeongMin Kim <86355699+Collection50@users.noreply.github.com>
Co-authored-by: GwanSik Kim <39869096+gwansikk@users.noreply.github.com>
  • Loading branch information
4 people authored Jul 19, 2024
1 parent 51d5594 commit 62705cf
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 19 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
52 changes: 45 additions & 7 deletions packages/react/src/hooks/useIsClient.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,50 @@
import { useIsomorphicLayoutEffect } from '@suspensive/utils'
import { renderHook } from '@testing-library/react'
import { useIsClient } from '.'
import { useState } from 'react'
import { useIsClient } from './useIsClient'

describe('useIsClient', () => {
it('should return true when client side painting start', () => {
const {
result: { current: isClient },
} = renderHook(() => useIsClient())

expect(isClient).toBe(true)
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)
const mockUseIsClient = vi.fn(useIsClient)
const { unmount } = renderHook(() => mockUseIsClient())
expect(mockUseIsClient).toBeCalledTimes(1)
unmount()
renderHook(() => mockUseIsClient())
expect(mockUseIsClient).toBeCalledTimes(2)
})
it('improve legacy useIsClientOnly', () => {
// check CSR environment first
expect(typeof document !== 'undefined').toBe(true)
let chanceIsClientToBeFalse = false
/**
* @deprecated This is legacy useIsClientOnly
*/
const mockUseIsClientLegacy = vi.fn(() => {
const [isClient, setIsClient] = useState(false)
if (!isClient) {
// eslint-disable-next-line react-compiler/react-compiler
chanceIsClientToBeFalse = true
}
useIsomorphicLayoutEffect(() => {
setIsClient(true)
}, [])
return isClient
})
const { unmount } = renderHook(() => mockUseIsClientLegacy())
expect(mockUseIsClientLegacy).toBeCalledTimes(2)
expect(chanceIsClientToBeFalse).toBe(true)
unmount()
renderHook(() => mockUseIsClientLegacy())
expect(mockUseIsClientLegacy).toBeCalledTimes(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 { useIsomorphicLayoutEffect } from '@suspensive/utils'
import { useState } from 'react'
import { noop } from '@suspensive/utils'
import { useSyncExternalStore } from 'react'

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 62705cf

Please sign in to comment.