From 9b7a44813124a8b2dfcc8f1d5418ff20d1d8fcac Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Fri, 25 Apr 2025 17:04:37 +0900 Subject: [PATCH 1/3] feat(hooks): add 'useIsClient' --- src/hooks/useIsClient/index.ts | 1 + src/hooks/useIsClient/useIsClient.spec.tsx | 32 +++++++++++++++ src/hooks/useIsClient/useIsClient.ts | 46 ++++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 src/hooks/useIsClient/index.ts create mode 100644 src/hooks/useIsClient/useIsClient.spec.tsx create mode 100644 src/hooks/useIsClient/useIsClient.ts diff --git a/src/hooks/useIsClient/index.ts b/src/hooks/useIsClient/index.ts new file mode 100644 index 0000000..b8d9782 --- /dev/null +++ b/src/hooks/useIsClient/index.ts @@ -0,0 +1 @@ +export { useIsClient } from './useIsClient.ts'; diff --git a/src/hooks/useIsClient/useIsClient.spec.tsx b/src/hooks/useIsClient/useIsClient.spec.tsx new file mode 100644 index 0000000..22c4eb2 --- /dev/null +++ b/src/hooks/useIsClient/useIsClient.spec.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { renderSSR } from '../../_internal/test-utils/renderSSR.tsx'; + +import { useIsClient } from './useIsClient.ts'; + +function TestComponent() { + const isClient = useIsClient(); + + return
{isClient ? 'Client-side' : 'Server-side'}
; +} + +describe('useIsClient', () => { + it('should render "Server-side" text when rendered in a server environment (SSR)', () => { + renderSSR.serverOnly(() => ); + + expect(screen.getByText('Server-side')).toBeInTheDocument(); + }); + + it('should update to "Client-side" text after hydration on the client', async () => { + await renderSSR(() => ); + + expect(screen.getByText('Client-side')).toBeInTheDocument(); + }); + + it('should render "Client-side" text when mounted directly on the client', async () => { + render(); + + expect(screen.getByText('Client-side')).toBeInTheDocument(); + }); +}); diff --git a/src/hooks/useIsClient/useIsClient.ts b/src/hooks/useIsClient/useIsClient.ts new file mode 100644 index 0000000..5b0af23 --- /dev/null +++ b/src/hooks/useIsClient/useIsClient.ts @@ -0,0 +1,46 @@ +import { useEffect, useState } from 'react'; + +/** + * @description + * `useIsClient` is a React hook that returns `true` only in the client-side environment. + * It is primarily used to differentiate between client-side and server-side rendering (SSR). + * The state is set to `true` only after the component is mounted in the client-side environment. + * + * @returns {boolean} Returns `true` in a client-side environment, and `false` otherwise. + * + * @example + * function MyComponent() { + * const isClient = useIsClient(); + * + * if (!isClient) { + * return
Loading...
; // Rendered on the server side + * } + * + * return
Client-side rendered content
; // Rendered on the client side + * } + * + * @example + * function MapComponent() { + * const isClient = useIsClient(); + * + * if (!isClient) return null; + * + * return
; + * } + * + * @example + * function UserTheme() { + * const isClient = useIsClient(); + * + * const theme = isClient ? localStorage.getItem('theme') : 'light'; + * + * return
Current theme: {theme}
; + * } + */ +export function useIsClient() { + const [isClient, setIsClient] = useState(false); + + useEffect(() => setIsClient(true), []); + + return isClient; +} From d906cc21b75e2449e5abec61e338fe3462fc3588 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Fri, 25 Apr 2025 17:16:04 +0900 Subject: [PATCH 2/3] feat(hooks/useIsClient): rename example functions for better clarity --- src/hooks/useIsClient/useIsClient.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useIsClient/useIsClient.ts b/src/hooks/useIsClient/useIsClient.ts index 5b0af23..ec01cfc 100644 --- a/src/hooks/useIsClient/useIsClient.ts +++ b/src/hooks/useIsClient/useIsClient.ts @@ -9,7 +9,7 @@ import { useEffect, useState } from 'react'; * @returns {boolean} Returns `true` in a client-side environment, and `false` otherwise. * * @example - * function MyComponent() { + * function ClientSideContent() { * const isClient = useIsClient(); * * if (!isClient) { @@ -20,7 +20,7 @@ import { useEffect, useState } from 'react'; * } * * @example - * function MapComponent() { + * function ClientOnlyMap() { * const isClient = useIsClient(); * * if (!isClient) return null; @@ -29,7 +29,7 @@ import { useEffect, useState } from 'react'; * } * * @example - * function UserTheme() { + * function ClientTheme() { * const isClient = useIsClient(); * * const theme = isClient ? localStorage.getItem('theme') : 'light'; From 3d22b831f0c3a702cf5ef8ce658e85f164a31130 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Thu, 8 May 2025 10:38:27 +0900 Subject: [PATCH 3/3] feat(src): add 'useIsClient' in 'index.ts' --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 5a5911d..d48d043 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ export { useImpressionRef } from './hooks/useImpressionRef/index.ts'; export { useInputState } from './hooks/useInputState/index.ts'; export { useIntersectionObserver } from './hooks/useIntersectionObserver/index.ts'; export { useInterval } from './hooks/useInterval/index.ts'; +export { useIsClient } from './hooks/useIsClient/index.ts'; export { useIsomorphicLayoutEffect } from './hooks/useIsomorphicLayoutEffect/index.ts'; export { useLoading } from './hooks/useLoading/index.ts'; export { useOutsideClickEffect } from './hooks/useOutsideClickEffect/index.ts';