Skip to content

Commit 57d5447

Browse files
committed
[IMPL] useMediaQuery hook
1 parent 8510890 commit 57d5447

12 files changed

Lines changed: 131 additions & 25 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useMediaQuery } from "../../../../../../packages/react-tools/src"
2+
3+
/**
4+
The component uses _useMediaQuery_ hook to detect color-scheme and displays result.
5+
*/
6+
export const UseMediaQuery = () => {
7+
const mediaQuery = useMediaQuery('(prefers-color-scheme: dark)');
8+
9+
return <div style={{textAlign: "left"}}>
10+
<p>
11+
Color-scheme: {mediaQuery.matches ? "dark" : "light"}
12+
</p>
13+
<p>
14+
Media matched: {mediaQuery.media}
15+
</p>
16+
</div>
17+
}

apps/react-tools-demo/src/components/hooks/useUpdate/UseUpdate.tsx renamed to apps/react-tools-demo/src/components/hooks/useRerender/UseRerender.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { useUpdate } from "../../../../../../packages/react-tools/src"
1+
import { useRerender } from "../../../../../../packages/react-tools/src"
22

33
/**
44
The component has:
55
- a button that execute the update
66
77
The component displays the current time. When button is clicked, it rerenders and current time is updated.
88
*/
9-
const UseUpdate = () => {
10-
const update = useUpdate();
9+
const UseRerender = () => {
10+
const update = useRerender();
1111

1212
return (<>
1313
<button type="button" onClick={update}>Update</button>
@@ -17,6 +17,6 @@ const UseUpdate = () => {
1717
</>);
1818
}
1919

20-
UseUpdate.displayName = "UseUpdate";
20+
UseRerender.displayName = "UseRerender";
2121

22-
export { UseUpdate };
22+
export { UseRerender };

apps/react-tools-demo/src/constants/components.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const COMPONENTS = [
2727
"useLayoutEffectCompare",
2828
"useLayoutEffectDeepCompare",
2929
"useLayoutEffectOnce",
30-
"useUpdate",
30+
"useRerender",
3131
"useIsMounted"
3232
],
3333
//PERFORMANCE
@@ -56,7 +56,8 @@ export const COMPONENTS = [
5656
"useInterval",
5757
"useTextSelection",
5858
"useDocumentVisibility",
59-
"useClipboard"
59+
"useClipboard",
60+
"useMediaQuery"
6061
]
6162
],
6263
//UTILS
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# useMediaQuery
2+
Hook to handle CSS mediaQuery. It returns an object with __matches__ and __media__ properties and receives an optional _onChange_ function to handle _MediaQueryListEvent change_ event.
3+
4+
## Usage
5+
6+
```tsx
7+
export const UseMediaQuery = () => {
8+
const mediaQuery = useMediaQuery('(prefers-color-scheme: dark)');
9+
10+
return <div style={{textAlign: "left"}}>
11+
<p>
12+
Color-scheme: {mediaQuery.matches ? "dark" : "light"}
13+
</p>
14+
<p>
15+
Media matched: {mediaQuery.media}
16+
</p>
17+
</div>
18+
}
19+
```
20+
21+
> The component uses _useMediaQuery_ hook to detect color-scheme and displays result.
22+
23+
24+
## API
25+
26+
```tsx
27+
useMediaQuery (mediaQuery: string, onChange?: (evt: MediaQueryListEvent) => void )
28+
```
29+
30+
> ### Params
31+
>
32+
> - __mediaQuery__: _string_
33+
media query to test.
34+
> - __onChange?__: _(evt: MediaQueryListEvent) => void_
35+
MediaQueryListEvent change handler.
36+
>
37+
38+
> ### Returns
39+
>
40+
> __result__: object with __matches__, boolean value that returns true if the document currently matches the media query, __media__, string that represents media query.
41+
> - __Object__:
42+
> - __matches__ : _boolean_
43+
> - __media__ : _string_
44+
>

apps/react-tools-demo/src/markdown/useUpdate.md renamed to apps/react-tools-demo/src/markdown/useRerender.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
# useUpdate
1+
# useRerender
22
Hook that force a render.
33

44
## Usage
55

66
```tsx
7-
const UseUpdate = () => {
8-
const update = useUpdate();
7+
const UseRerender = () => {
8+
const update = useRerender();
99

1010
return (<>
1111
<button type="button" onClick={update}>Update</button>
@@ -15,9 +15,9 @@ const UseUpdate = () => {
1515
</>);
1616
}
1717

18-
UseUpdate.displayName = "UseUpdate";
18+
UseRerender.displayName = "UseRerender";
1919

20-
export { UseUpdate };
20+
export { UseRerender };
2121
```
2222

2323
> The component has:
@@ -29,7 +29,7 @@ export { UseUpdate };
2929
## API
3030

3131
```tsx
32-
useUpdate (): React.DispatchWithoutAction
32+
useRerender (): React.DispatchWithoutAction
3333
```
3434

3535
> ### Params

packages/react-tools/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
- [x] useLayoutEffectCompare
2525
- [x] useLayoutEffectDeepCompare
2626
- [x] useLayoutEffectOnce
27-
- [x] useUpdate
27+
- [x] useRerender
2828
- [x] useIsMounted
2929
- [ ] useDeferredValue (polyfill - an idea can be use setTimeout inside useEffect)
3030

@@ -45,7 +45,6 @@
4545
- [ ] useKeysEvents
4646
- [ ] useSize
4747
- [ ] useHover
48-
- [ ] useMediaQuery
4948
- [ ] useResponsive
5049
- [ ] useClickOutside
5150
- [ ] useScrollIntoView
@@ -67,6 +66,7 @@
6766
- [x] useTextSelection
6867
- [x] useDocumentVisibility
6968
- [x] useClipboard
69+
- [x] useMediaQuery
7070
- [ ] useColorScheme
7171
- [ ] useTitle (change document.title but also document.head.title nodeElement)
7272
- [ ] useFetch

packages/react-tools/src/hooks/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export { usePublishSubscribe } from './usePublishSubscribe';
2525
export { useEvents } from './useEvents';
2626
export { useEventDispatcher } from './useEventDispatcher';
2727
export { useEventListener } from './useEventListener';
28-
export { useUpdate } from './useUpdate';
28+
export { useRerender } from './useRerender';
2929
export { usePerformAction } from './usePerformAction';
3030
export { useScript } from './useScript';
3131
export { useSyncExternalStore } from './useSyncExternalStore';
@@ -37,4 +37,5 @@ export { useTimeout } from './useTimeout';
3737
export { useInterval } from './useInterval';
3838
export { useTextSelection } from './useTextSelection';
3939
export { useDocumentVisibility } from './useDocumentVisibility';
40-
export { useClipboard } from './useClipboard';
40+
export { useClipboard } from './useClipboard';
41+
export { useMediaQuery } from './useMediaQuery';

packages/react-tools/src/hooks/useEffectOnce.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { EffectCallback, useEffect, useRef } from "react";
2-
import { useUpdate } from "."
2+
import { useRerender } from "."
33

44
/**
55
* **`useEffectOnce`**: Hook to executes _effect_ and _clean up_ after component mount __only once__. It prevents _React 18 StrictMode_ behavior if present, otherwise it works like a normal _useEffect_ with empty dependencies array. __*N.B.*__ Not use in a component with normal _useEffect_, if it executes a _React.DispatchAction_, because this action is executes twice if there is _React.StrictMode_.
66
* @param {EffectCallback} effect
77
*/
88
export const useEffectOnce = (effect: EffectCallback) => {
9-
const update = useUpdate();
9+
const update = useRerender();
1010
const effectFn = useRef(effect);
1111
const cleanUpFn = useRef<ReturnType<EffectCallback>>();
1212
const effectCalled = useRef(false);

packages/react-tools/src/hooks/useLayoutEffectOnce.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { EffectCallback, useLayoutEffect, useRef } from "react";
2-
import { useUpdate } from "."
2+
import { useRerender } from "."
33

44
/**
55
* **`useLayoutEffectOnce`**: Hook to executes _effect_ and _clean up_ after component mount __only once__. It prevents _React 18 StrictMode_ behavior if present, otherwise it works like a normal _useLayoutEffect_ with empty dependencies array. __*N.B.*__ Not use in a component with normal _useLayoutEffect_, if it executes a _React.DispatchAction_, because this action is executes twice if there is _React.StrictMode_.
66
* @param {EffectCallback} effect
77
*/
88
export const useLayoutEffectOnce = (effect: EffectCallback) => {
9-
const update = useUpdate();
9+
const update = useRerender();
1010
const effectFn = useRef(effect);
1111
const cleanUpFn = useRef<ReturnType<EffectCallback>>();
1212
const effectCalled = useRef(false);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useCallback, useRef } from "react"
2+
import { useSyncExternalStore } from "."
3+
4+
/**
5+
* **`useMediaQuery`**: Hook to handle CSS mediaQuery. It returns an object with __matches__ and __media__ properties and receives an optional _onChange_ function to handle _MediaQueryListEvent change_ event.
6+
* @param {string} mediaQuery - media query to test.
7+
* @param {(evt: MediaQueryListEvent) => void} [onChange] - MediaQueryListEvent change handler.
8+
* @returns {{matches: boolean, media: string}} result - object with __matches__, boolean value that returns true if the document currently matches the media query, __media__, string that represents media query.
9+
*/
10+
export const useMediaQuery = (mediaQuery: string, onChange?: (evt: MediaQueryListEvent) => void ): {matches: boolean, media: string} => {
11+
const cachedMediaQuery = useRef((() => {
12+
const {media, matches} = window.matchMedia(mediaQuery);
13+
return {
14+
matches,
15+
media
16+
}
17+
})());
18+
19+
return useSyncExternalStore(
20+
useCallback(notif => {
21+
const mq = window.matchMedia(mediaQuery);
22+
const listener = (evt: MediaQueryListEvent) => {
23+
!!onChange && onChange(evt);
24+
notif();
25+
}
26+
mq.addEventListener("change", listener);
27+
return () => {
28+
mq.removeEventListener("change", listener);
29+
}
30+
}, [mediaQuery, onChange]),
31+
useCallback(() => {
32+
const {media, matches} = window.matchMedia(mediaQuery);
33+
if (matches !== cachedMediaQuery.current.matches) {
34+
cachedMediaQuery.current = {
35+
matches,
36+
media
37+
};
38+
}
39+
return cachedMediaQuery.current;
40+
}, [mediaQuery])
41+
)
42+
}

0 commit comments

Comments
 (0)