Skip to content

Commit d2c3afd

Browse files
author
Leroy Korterink
committed
Make hooks client-side rendering and server-side rendering compatible
1 parent aba651b commit d2c3afd

5 files changed

Lines changed: 51 additions & 14 deletions

File tree

src/hooks/useContentRect/useContentRect.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import { useResizeObserver } from '../useResizeObserver/useResizeObserver.js';
99
*/
1010
export function useContentRect(
1111
target: Unreffable<Element | null>,
12+
serverSideRendering = false,
1213
): RefObject<DOMRectReadOnly | null> {
13-
const contentRectRef = useRef<DOMRectReadOnly | null>(null);
14+
const contentRectRef = useRef<DOMRectReadOnly | null>(
15+
serverSideRendering ? null : unref(target)?.getBoundingClientRect() ?? null,
16+
);
1417

1518
const onResize = useCallback<ResizeObserverCallback>((entries): void => {
1619
contentRectRef.current = entries.at(0)?.contentRect ?? null;

src/hooks/useContentRectState/useContentRectState.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ import { useResizeObserver } from '../useResizeObserver/useResizeObserver.js';
77
* A hook that returns the content rectangle of the target element.
88
* The content rectangle is updated whenever the target element is resized.
99
*/
10-
export function useContentRectState(target: Unreffable<Element | null>): DOMRectReadOnly | null {
11-
const [contentRect, setContentRect] = useState<DOMRectReadOnly | null>(null);
10+
export function useContentRectState(
11+
target: Unreffable<Element | null>,
12+
serverSideRendering = false,
13+
): DOMRectReadOnly | null {
14+
const [contentRect, setContentRect] = useState<DOMRectReadOnly | null>(
15+
serverSideRendering ? null : unref(target)?.getBoundingClientRect() ?? null,
16+
);
1217
const rafRef = useRef(0);
1318

1419
const onResize = useCallback<ResizeObserverCallback>((entries) => {

src/hooks/useMediaDuration/useMediaDuration.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import { type MutableRefObject, useCallback, useEffect, useState } from 'react';
66
export function useMediaDuration(
77
mediaElementRef: MutableRefObject<HTMLMediaElement | null>,
88
): number {
9-
const [mediaDuration, setMediaDuration] = useState<number>(Number.NaN);
9+
const [mediaDuration, setMediaDuration] = useState<number>(
10+
mediaElementRef.current?.duration ?? Number.NaN,
11+
);
1012

1113
const updateDuration = useCallback(() => {
1214
setMediaDuration(mediaElementRef.current?.duration ?? Number.NaN);

src/hooks/useMediaQuery/useMediaQuery.mdx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,48 @@ Hook that returns true when the media query matches.
1212
function useMediaQuery(mediaQueryOrVariableName: string, defaultValue = false): boolean;
1313
```
1414

15-
## Usage
15+
## Using a media query string
1616

17-
Define the media query in your CSS variables.
17+
Use the hook in your component using a media query string.
18+
19+
```tsx
20+
function DemoComponent() {
21+
const isMinWidth480px = useMediaQuery('(min-width: 480px)');
22+
23+
return <div>{isMinWidth480px ? 'large' : 'small'}</div>;
24+
}
25+
```
26+
27+
## Using a CSS variable
28+
29+
To use the hook in your component using the CSS variable, define the media query in your CSS
30+
variables.
1831

1932
```css
2033
:root {
2134
--min-width-480: (min-width: 480px);
2235
}
2336
```
2437

25-
Use the hook in your component using the CSS variable or a custom media query.
38+
Use the media query variable as the first argument.
39+
40+
```tsx
41+
function DemoComponent() {
42+
const isMinWidth480px = useMediaQuery('--min-width-480');
43+
44+
return <div>{isMinWidth480px ? 'large' : 'small'}</div>;
45+
}
46+
```
47+
48+
## Server-side rendering
49+
50+
The hook returns the default value when the `matchMedia` API is not available (for example in
51+
server-side rendering). Set a default value if you get hydration mismatch errors.
2652

2753
```tsx
2854
function DemoComponent() {
29-
const isMinWidth480pxUsingVar = useMediaQuery('--min-width-480');
30-
const isMinWidth480pxUsingQuery = useMediaQuery('(min-width: 480px)');
55+
const isMinWidth480px = useMediaQuery('(min-width: 480px)', true);
3156

32-
return <div>{isMinWidth480pxUsingVar && isMinWidth480pxUsingQuery ? 'large' : 'small'}</div>;
57+
return <div>{isMinWidth480px ? 'large' : 'small'}</div>;
3358
}
3459
```

src/hooks/useMediaQuery/useMediaQuery.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,18 @@ export function getMediaQueryList(
4949
* Hook that returns a boolean indicating whether the media query matches.
5050
*
5151
* @param mediaQueryOrVariableName - The name of the CSS variable that describes the media query.
52-
* @param defaultValue - The default value to return if the matchMedia API is not available.
52+
* @param defaultValue - The default value to return if the matchMedia API is not available (set a value to make this hook work in SSR mode).
5353
*/
5454
export function useMediaQuery(
5555
mediaQueryOrVariableName: MediaQueryValues,
56-
defaultValue = false,
57-
): boolean {
56+
defaultValue?: boolean,
57+
): boolean | undefined {
5858
const [mediaQueryList, setMediaQueryList] = useState<MediaQueryList | undefined>(() =>
5959
getMediaQueryList(mediaQueryOrVariableName),
6060
);
61-
const [matches, setMatches] = useState<boolean | undefined>(defaultValue);
61+
const [matches, setMatches] = useState<boolean | undefined>(
62+
defaultValue === undefined ? mediaQueryList?.matches : defaultValue,
63+
);
6264

6365
useEffect(() => {
6466
const newMediaQueryList = getMediaQueryList(mediaQueryOrVariableName);

0 commit comments

Comments
 (0)