Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add useMediaQuery #2455

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export const menus = [
'useScroll',
'useSize',
'useFocusWithin',
'useMediaQuery',
],
},
{
Expand Down
2 changes: 2 additions & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import useVirtualList from './useVirtualList';
import useWebSocket from './useWebSocket';
import useWhyDidYouUpdate from './useWhyDidYouUpdate';
import useMutationObserver from './useMutationObserver';
import useMediaQuery from './useMediaQuery';

export {
useRequest,
Expand Down Expand Up @@ -156,4 +157,5 @@ export {
useRafTimeout,
useResetState,
useMutationObserver,
useMediaQuery,
};
42 changes: 42 additions & 0 deletions packages/hooks/src/useMediaQuery/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { renderHook, act } from '../../utils/tests';
import useMediaQuery from '..';

Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

describe('useMediaQuery', () => {
function changeWidth(width: number) {
act(() => {
(global as any).innerWidth = width;
(global as any).dispatchEvent(new Event('resize'));
});
}
changeWidth(1024);

it('should be defined', () => {
expect(useMediaQuery).toBeDefined();
});

it('should return the correct value', () => {
const { result } = renderHook(() => useMediaQuery(['(min-width: 1024px)'], [true], false));
expect(result.current[0]).toBe(false);
});

it('should return the correct value when the media query changes', () => {
const { result } = renderHook(() => useMediaQuery(['(min-width: 1024px)'], [true], false));
expect(result.current[0]).toBe(false);
changeWidth(768);
expect(result.current[0]).toBe(false);
});
});
25 changes: 25 additions & 0 deletions packages/hooks/src/useMediaQuery/demo/demo1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* title: Get responsive info in components
* desc: By calling useMediaQuery in components, you can retrieve the responsive infomation of the browser page and subscribe to it at the same time.
*
* title.zh-CN: 在组件中获取响应式信息
* desc.zh-CN: 在组件中调用 useMediaQuery 可以获取并订阅浏览器窗口的响应式信息。
*/

import React from 'react';
import { useMediaQuery } from 'ahooks';

export default function () {
const [value, currentQuery] = useMediaQuery(
['(min-width: 1024px)', '(min-width: 768px)', '(min-width: 320px)'],
[3, 2, 1],
0,
);

return (
<div>
<div>currentValue: {value.toString()}</div>
<div>currentQuery: {currentQuery}</div>
</div>
);
}
25 changes: 25 additions & 0 deletions packages/hooks/src/useMediaQuery/index.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
nav:
path: /hooks
---

# useMediaQuery

React Hook for getting responsive info.

## Examples

### Get responsive info in components

<code src="./demo/demo1.tsx" />

## API

```typescript
interface ResponsiveInfo {
value: number;
currentQuery: string;
}

function useMediaQuery(queries: K[], values: T[], defaultValue: T): ResponsiveInfo;
```
32 changes: 32 additions & 0 deletions packages/hooks/src/useMediaQuery/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useRef, useState } from 'react';

function useMediaQuery<K, T>(queries: K[], values: T[], defaultValue: T) {
const mediaQueryLists = useRef<MediaQueryList[]>([]);
const [value, setValue] = useState<T>(defaultValue);
const [currentQuery, setCurrentQuery] = useState<string>((queries as MediaQueryList[])[0].media);

const getValue = () => {
const index = mediaQueryLists.current.findIndex((mql) => mql.matches);
setCurrentQuery(mediaQueryLists.current?.[index]?.media || '');
return values?.[index] || defaultValue;
};

const handleQueryListener = () => setValue(getValue());

const handleMediaQueryLists = () => {
mediaQueryLists.current.forEach((mql) =>
mql.removeEventListener('change', handleQueryListener),
);
mediaQueryLists.current = queries.map((query) => query && window.matchMedia(query as string));
mediaQueryLists.current.forEach((mql) => mql.addEventListener('change', handleQueryListener));
setValue(getValue());
};

useState(() => {
handleMediaQueryLists();
});

return [value, currentQuery];
}

export default useMediaQuery;
25 changes: 25 additions & 0 deletions packages/hooks/src/useMediaQuery/index.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
nav:
path: /hooks
---

# useMediaQuery

获取响应式信息。

## 代码演示

### 在组件中获取响应式信息

<code src="./demo/demo1.tsx" />

## API

```typescript
interface ResponsiveInfo {
value: number;
currentQuery: string;
}

function useMediaQuery(queries: K[], values: T[], defaultValue: T): ResponsiveInfo;
```