Skip to content

Commit

Permalink
Make hooks client-side rendering and server-side rendering compatible
Browse files Browse the repository at this point in the history
  • Loading branch information
leroykorterink committed May 3, 2024
1 parent aba651b commit d2c3afd
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 14 deletions.
5 changes: 4 additions & 1 deletion src/hooks/useContentRect/useContentRect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import { useResizeObserver } from '../useResizeObserver/useResizeObserver.js';
*/
export function useContentRect(
target: Unreffable<Element | null>,
serverSideRendering = false,
): RefObject<DOMRectReadOnly | null> {
const contentRectRef = useRef<DOMRectReadOnly | null>(null);
const contentRectRef = useRef<DOMRectReadOnly | null>(
serverSideRendering ? null : unref(target)?.getBoundingClientRect() ?? null,
);

const onResize = useCallback<ResizeObserverCallback>((entries): void => {
contentRectRef.current = entries.at(0)?.contentRect ?? null;
Expand Down
9 changes: 7 additions & 2 deletions src/hooks/useContentRectState/useContentRectState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import { useResizeObserver } from '../useResizeObserver/useResizeObserver.js';
* A hook that returns the content rectangle of the target element.
* The content rectangle is updated whenever the target element is resized.
*/
export function useContentRectState(target: Unreffable<Element | null>): DOMRectReadOnly | null {
const [contentRect, setContentRect] = useState<DOMRectReadOnly | null>(null);
export function useContentRectState(
target: Unreffable<Element | null>,
serverSideRendering = false,
): DOMRectReadOnly | null {
const [contentRect, setContentRect] = useState<DOMRectReadOnly | null>(
serverSideRendering ? null : unref(target)?.getBoundingClientRect() ?? null,
);
const rafRef = useRef(0);

const onResize = useCallback<ResizeObserverCallback>((entries) => {
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/useMediaDuration/useMediaDuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { type MutableRefObject, useCallback, useEffect, useState } from 'react';
export function useMediaDuration(
mediaElementRef: MutableRefObject<HTMLMediaElement | null>,
): number {
const [mediaDuration, setMediaDuration] = useState<number>(Number.NaN);
const [mediaDuration, setMediaDuration] = useState<number>(
mediaElementRef.current?.duration ?? Number.NaN,
);

const updateDuration = useCallback(() => {
setMediaDuration(mediaElementRef.current?.duration ?? Number.NaN);
Expand Down
37 changes: 31 additions & 6 deletions src/hooks/useMediaQuery/useMediaQuery.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,48 @@ Hook that returns true when the media query matches.
function useMediaQuery(mediaQueryOrVariableName: string, defaultValue = false): boolean;
```

## Usage
## Using a media query string

Define the media query in your CSS variables.
Use the hook in your component using a media query string.

```tsx
function DemoComponent() {
const isMinWidth480px = useMediaQuery('(min-width: 480px)');

return <div>{isMinWidth480px ? 'large' : 'small'}</div>;
}
```

## Using a CSS variable

To use the hook in your component using the CSS variable, define the media query in your CSS
variables.

```css
:root {
--min-width-480: (min-width: 480px);
}
```

Use the hook in your component using the CSS variable or a custom media query.
Use the media query variable as the first argument.

```tsx
function DemoComponent() {
const isMinWidth480px = useMediaQuery('--min-width-480');

return <div>{isMinWidth480px ? 'large' : 'small'}</div>;
}
```

## Server-side rendering

The hook returns the default value when the `matchMedia` API is not available (for example in
server-side rendering). Set a default value if you get hydration mismatch errors.

```tsx
function DemoComponent() {
const isMinWidth480pxUsingVar = useMediaQuery('--min-width-480');
const isMinWidth480pxUsingQuery = useMediaQuery('(min-width: 480px)');
const isMinWidth480px = useMediaQuery('(min-width: 480px)', true);

return <div>{isMinWidth480pxUsingVar && isMinWidth480pxUsingQuery ? 'large' : 'small'}</div>;
return <div>{isMinWidth480px ? 'large' : 'small'}</div>;
}
```
10 changes: 6 additions & 4 deletions src/hooks/useMediaQuery/useMediaQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,18 @@ export function getMediaQueryList(
* Hook that returns a boolean indicating whether the media query matches.
*
* @param mediaQueryOrVariableName - The name of the CSS variable that describes the media query.
* @param defaultValue - The default value to return if the matchMedia API is not available.
* @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).
*/
export function useMediaQuery(
mediaQueryOrVariableName: MediaQueryValues,
defaultValue = false,
): boolean {
defaultValue?: boolean,
): boolean | undefined {
const [mediaQueryList, setMediaQueryList] = useState<MediaQueryList | undefined>(() =>
getMediaQueryList(mediaQueryOrVariableName),
);
const [matches, setMatches] = useState<boolean | undefined>(defaultValue);
const [matches, setMatches] = useState<boolean | undefined>(
defaultValue === undefined ? mediaQueryList?.matches : defaultValue,
);

useEffect(() => {
const newMediaQueryList = getMediaQueryList(mediaQueryOrVariableName);
Expand Down

0 comments on commit d2c3afd

Please sign in to comment.