From 665d999cb6e543a988e675d50fa7fdcaf7ce3027 Mon Sep 17 00:00:00 2001 From: Shiny <contact@shinychang.net> Date: Sat, 30 May 2020 00:40:10 +0800 Subject: [PATCH] Add useOnline and useSize --- .storybook/main.js | 4 ++-- package.json | 3 ++- src/hooks/useFPS.js | 5 +++-- src/hooks/useOnline.js | 18 ++++++++++++++++++ src/hooks/useSize.js | 30 ++++++++++++++++++++++++++++++ src/index.js | 4 +++- stories/useFPS.stories.js | 2 -- stories/useOnline.stories.js | 11 +++++++++++ stories/useSize.stories.js | 18 ++++++++++++++++++ tests/hooks/useOnline.test.js | 28 ++++++++++++++++++++++++++++ yarn.lock | 35 +++++++++++++++++++++++++++++++++++ 11 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 src/hooks/useOnline.js create mode 100644 src/hooks/useSize.js create mode 100644 stories/useOnline.stories.js create mode 100644 stories/useSize.stories.js create mode 100644 tests/hooks/useOnline.test.js diff --git a/.storybook/main.js b/.storybook/main.js index 3902cf2..c1f6a60 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,9 +1,9 @@ module.exports = { stories: ['../stories/**/*.stories.js'], - addons: ['@storybook/addon-actions', '@storybook/addon-links'], + addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/addon-storysource'], webpackFinal: async config => { // do mutation to the config return config; - }, + } }; diff --git a/package.json b/package.json index 48ce2e2..90b35e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shinychang/hooks", - "version": "0.1.3", + "version": "0.2.0", "module": "src/index.js", "main": "lib/index.js", "repository": "https://github.com/ShinyChang/hooks.git", @@ -25,6 +25,7 @@ "@babel/preset-react": "^7.10.1", "@storybook/addon-actions": "^5.3.19", "@storybook/addon-links": "^5.3.19", + "@storybook/addon-storysource": "^5.3.19", "@storybook/addons": "^5.3.19", "@storybook/react": "^5.3.19", "babel-loader": "^8.1.0", diff --git a/src/hooks/useFPS.js b/src/hooks/useFPS.js index 1f57894..1a6becf 100644 --- a/src/hooks/useFPS.js +++ b/src/hooks/useFPS.js @@ -10,8 +10,9 @@ const useFPS = (maxDisplayFrameRate = 60) => { const frame = () => { const now = performance.now(); if (lastTime + 1000 <= now) { - if (mounted && count !== fps) { - setFPS(Math.min(count, maxDisplayFrameRate)); + const displayFps = Math.min(count, maxDisplayFrameRate); + if (mounted && displayFps !== fps) { + setFPS(displayFps); } lastTime = now; count = 0; diff --git a/src/hooks/useOnline.js b/src/hooks/useOnline.js new file mode 100644 index 0000000..1b8ae00 --- /dev/null +++ b/src/hooks/useOnline.js @@ -0,0 +1,18 @@ +import { useEffect, useState } from 'react'; + +const useOnline = () => { + const [online, setOnline] = useState(navigator.onLine); + useEffect(() => { + const onOnline = () => setOnline(true); + const onOffline = () => setOnline(false); + window.addEventListener('online', onOnline); + window.addEventListener('offline', onOffline); + return () => { + window.removeEventListener('online', onOnline); + window.removeEventListener('offline', onOffline); + }; + }); + return online; +}; + +export default useOnline; diff --git a/src/hooks/useSize.js b/src/hooks/useSize.js new file mode 100644 index 0000000..06fc0b2 --- /dev/null +++ b/src/hooks/useSize.js @@ -0,0 +1,30 @@ +import { useEffect, useState } from 'react'; + +const useSize = ref => { + const [size, setSize] = useState(() => { + if (!ref.current) { + return [0, 0]; + } + const { width, height } = ref.current.getBoundingClientRect(); + return [width, height]; + }); + + useEffect(() => { + const resizeObserver = new ResizeObserver(entries => { + const { width, height } = entries[0].contentRect; + setSize([width, height]); + }); + if (ref.current) { + resizeObserver.observe(ref.current); + } + return () => { + if (ref.current) { + resizeObserver.unobserve(ref.current); + } + }; + }, [ref.current]); + + return size; +}; + +export default useSize; diff --git a/src/index.js b/src/index.js index 4bb636b..7ba8707 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,5 @@ import useFPS from './hooks/useFPS'; +import useOnline from './hooks/useOnline'; +import useSize from './hooks/useSize'; -export { useFPS }; +export { useFPS, useOnline, useSize }; diff --git a/stories/useFPS.stories.js b/stories/useFPS.stories.js index 2e0aa38..37cc9d6 100644 --- a/stories/useFPS.stories.js +++ b/stories/useFPS.stories.js @@ -1,5 +1,3 @@ -import React from 'react'; - import { useFPS } from '../src'; export default { diff --git a/stories/useOnline.stories.js b/stories/useOnline.stories.js new file mode 100644 index 0000000..06b25d6 --- /dev/null +++ b/stories/useOnline.stories.js @@ -0,0 +1,11 @@ +import { useOnline } from '../src'; + +export default { + title: 'useOnline', + component: useOnline +}; + +export const Basic = () => { + const online = useOnline(); + return `Online: ${online}`; +}; diff --git a/stories/useSize.stories.js b/stories/useSize.stories.js new file mode 100644 index 0000000..0b31a9d --- /dev/null +++ b/stories/useSize.stories.js @@ -0,0 +1,18 @@ +import React, { useRef } from 'react'; +import { useSize } from '../src'; + +export default { + title: 'useSize', + component: useSize +}; + +export const Basic = () => { + const ref = useRef(null); + const size = useSize(ref); + const noop = () => {}; + return ( + <div> + <textarea style={{ resize: 'both', overflow: 'auto' }} ref={ref} value={size} onChange={noop} /> + </div> + ); +}; diff --git a/tests/hooks/useOnline.test.js b/tests/hooks/useOnline.test.js new file mode 100644 index 0000000..1173bc1 --- /dev/null +++ b/tests/hooks/useOnline.test.js @@ -0,0 +1,28 @@ +import { renderHook, act } from '@testing-library/react-hooks'; + +import { useOnline } from '../../src'; + +jest.spyOn(navigator, 'onLine', 'get').mockReturnValue(false); + +test('default online is navigator.onLine', () => { + const { result } = renderHook(() => useOnline()); + expect(result.current).toBe(false); +}); + +test('change online when online/offline event fired', () => { + const map = {}; + window.addEventListener = jest.fn((name, cb) => { + map[name] = cb; + }); + const { result } = renderHook(() => useOnline()); + + act(() => { + map['online'](); + }); + expect(result.current).toBe(true); + + act(() => { + map['offline'](); + }); + expect(result.current).toBe(false); +}); diff --git a/yarn.lock b/yarn.lock index 20542f0..f2e835f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1500,6 +1500,25 @@ qs "^6.6.0" ts-dedent "^1.1.0" +"@storybook/addon-storysource@^5.3.19": + version "5.3.19" + resolved "https://registry.yarnpkg.com/@storybook/addon-storysource/-/addon-storysource-5.3.19.tgz#ae693e88db5d220cb256a9ef4a2366c300e8d88c" + integrity sha512-W7mIAHuxYT+b1huaHCHLkBAh2MbeWmF8CxeBCFiOgZaYYQUTDEh018HJF8u2AqiWSouRhcfzhTnGxOo0hNRBgw== + dependencies: + "@storybook/addons" "5.3.19" + "@storybook/components" "5.3.19" + "@storybook/router" "5.3.19" + "@storybook/source-loader" "5.3.19" + "@storybook/theming" "5.3.19" + core-js "^3.0.1" + estraverse "^4.2.0" + loader-utils "^1.2.3" + prettier "^1.16.4" + prop-types "^15.7.2" + react-syntax-highlighter "^11.0.2" + regenerator-runtime "^0.13.3" + util-deprecate "^1.0.2" + "@storybook/addons@5.3.19", "@storybook/addons@^5.3.19": version "5.3.19" resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.3.19.tgz#3a7010697afd6df9a41b8c8a7351d9a06ff490a4" @@ -1805,6 +1824,22 @@ qs "^6.6.0" util-deprecate "^1.0.2" +"@storybook/source-loader@5.3.19": + version "5.3.19" + resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-5.3.19.tgz#ff0a00731c24c61721d8b9d84152f8542913a3b7" + integrity sha512-srSZRPgEOUse8nRVnlazweB2QGp63mPqM0uofg8zYARyaYSOzkC155ymdeiHsmiBTS3X3I0FQE4+KnwiH7iLtw== + dependencies: + "@storybook/addons" "5.3.19" + "@storybook/client-logger" "5.3.19" + "@storybook/csf" "0.0.1" + core-js "^3.0.1" + estraverse "^4.2.0" + global "^4.3.2" + loader-utils "^1.2.3" + prettier "^1.16.4" + prop-types "^15.7.2" + regenerator-runtime "^0.13.3" + "@storybook/theming@5.3.19": version "5.3.19" resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.3.19.tgz#177d9819bd64f7a1a6ea2f1920ffa5baf9a5f467"