Skip to content

Commit

Permalink
Merge branch 'master' into fix/userAntdTable-reset
Browse files Browse the repository at this point in the history
  • Loading branch information
hchlq authored Apr 27, 2023
2 parents f06ed57 + b92ca0e commit dde3385
Show file tree
Hide file tree
Showing 38 changed files with 10,664 additions and 3,556 deletions.
2 changes: 1 addition & 1 deletion packages/hooks/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ahooks",
"version": "3.7.5",
"version": "3.7.6",
"description": "react hooks library",
"keywords": [
"ahooks",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,10 @@ describe('useStorageState', () => {
hook.result.current.setState(undefined);
});
expect(hook.result.current.state).toBeUndefined();

act(() => hook.result.current.setState('value'));
expect(hook.result.current.state).toBe('value');
act(() => hook.result.current.setState());
expect(hook.result.current.state).toBeUndefined();
});
});
56 changes: 26 additions & 30 deletions packages/hooks/src/createUseStorageState/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,23 @@ export interface Options<T> {
serializer?: (value: T) => string;
deserializer?: (value: string) => T;
defaultValue?: T | IFuncUpdater<T>;
onError?: (error: unknown) => void;
}

export function createUseStorageState(getStorage: () => Storage | undefined) {
function useStorageState<T>(key: string, options?: Options<T>) {
function useStorageState<T>(key: string, options: Options<T> = {}) {
let storage: Storage | undefined;
const {
onError = (e) => {
console.error(e);
},
} = options;

// https://github.com/alibaba/hooks/issues/800
try {
storage = getStorage();
} catch (err) {
console.error(err);
onError(err);
}

const serializer = (value: T) => {
Expand All @@ -35,57 +41,47 @@ export function createUseStorageState(getStorage: () => Storage | undefined) {
return JSON.stringify(value);
};

const deserializer = (value: string) => {
const deserializer = (value: string): T => {
if (options?.deserializer) {
return options?.deserializer(value);
}
return JSON.parse(value);
};

function getDefaultValue() {
return isFunction(options?.defaultValue) ? options?.defaultValue() : options?.defaultValue;
}

function setStoredValue(value?: T) {
if (isUndef(value)) {
storage?.removeItem(key);
} else {
try {
storage?.setItem(key, serializer(value));
} catch (e) {
console.error(e);
}
}
}

function getStoredValue() {
try {
const raw = storage?.getItem(key);
if (raw) {
return deserializer(raw);
}
} catch (e) {
console.error(e);
onError(e);
}

const defaultValue = getDefaultValue();

setStoredValue(defaultValue);

return defaultValue;
if (isFunction(options?.defaultValue)) {
return options?.defaultValue();
}
return options?.defaultValue;
}

const [state, setState] = useState<T>(() => getStoredValue());
const [state, setState] = useState(() => getStoredValue());

useUpdateEffect(() => {
setState(getStoredValue());
}, [key]);

const updateState = (value: T | IFuncUpdater<T>) => {
const updateState = (value?: T | IFuncUpdater<T>) => {
const currentState = isFunction(value) ? value(state) : value;

setState(currentState);
setStoredValue(currentState);

if (isUndef(currentState)) {
storage?.removeItem(key);
} else {
try {
storage?.setItem(key, serializer(currentState));
} catch (e) {
console.error(e);
}
}
};

return [state, useMemoizedFn(updateState)] as const;
Expand Down
68 changes: 29 additions & 39 deletions packages/hooks/src/useCookieState/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useState } from 'react';
import { renderHook, act, render, fireEvent } from '@testing-library/react';
import { renderHook, act } from '@testing-library/react';
import useCookieState from '../index';
import type { Options } from '../index';
import Cookies from 'js-cookie';
Expand All @@ -14,43 +13,6 @@ describe('useCookieState', () => {
} as const;
});

it('defaultValue should work', () => {
const COOKIE = {
KEY: 'test-key-with-default-value',
KEY2: 'test-key-with-default-value2',
DEFAULT_VALUE: 'A',
DEFAULT_VALUE2: 'A2',
};
const Setup = () => {
const [key, setKey] = useState<string>(COOKIE.KEY);
const [defaultValue, setDefaultValue] = useState<string>(COOKIE.DEFAULT_VALUE);
const [state] = useCookieState(key, { defaultValue });

return (
<>
<div role="state">{state}</div>
<button
role="button"
onClick={() => {
setKey(COOKIE.KEY2);
setDefaultValue(COOKIE.DEFAULT_VALUE2);
}}
/>
</>
);
};
const wrap = render(<Setup />);

// Initial value
expect(wrap.getByRole('state').textContent).toBe(COOKIE.DEFAULT_VALUE);
expect(Cookies.get(COOKIE.KEY)).toBe(COOKIE.DEFAULT_VALUE);

// Change `key` and `defaultValue`
act(() => fireEvent.click(wrap.getByRole('button')));
expect(Cookies.get(COOKIE.KEY)).toBe(COOKIE.DEFAULT_VALUE);
expect(Cookies.get(COOKIE.KEY2)).toBe(COOKIE.DEFAULT_VALUE2);
});

it('getKey should work', () => {
const COOKIE = 'test-key';
const hook = setUp(COOKIE, {
Expand All @@ -70,6 +32,7 @@ describe('useCookieState', () => {
});
expect(anotherHook.result.current.state).toBe('C');
expect(hook.result.current.state).toBe('B');
expect(Cookies.get(COOKIE)).toBe('C');
});

it('should support undefined', () => {
Expand All @@ -86,6 +49,13 @@ describe('useCookieState', () => {
defaultValue: 'false',
});
expect(anotherHook.result.current.state).toBe('false');
expect(Cookies.get(COOKIE)).toBeUndefined();
act(() => {
// @ts-ignore
hook.result.current.setState();
});
expect(hook.result.current.state).toBeUndefined();
expect(Cookies.get(COOKIE)).toBeUndefined();
});

it('should support empty string', () => {
Expand All @@ -109,4 +79,24 @@ describe('useCookieState', () => {
});
expect(hook.result.current.state).toBe('hello world, zhangsan');
});

it('using the same cookie name', () => {
const COOKIE_NAME = 'test-same-cookie-name';
const { result: result1 } = setUp(COOKIE_NAME, { defaultValue: 'A' });
const { result: result2 } = setUp(COOKIE_NAME, { defaultValue: 'B' });
expect(result1.current.state).toBe('A');
expect(result2.current.state).toBe('B');
act(() => {
result1.current.setState('C');
});
expect(result1.current.state).toBe('C');
expect(result2.current.state).toBe('B');
expect(Cookies.get(COOKIE_NAME)).toBe('C');
act(() => {
result2.current.setState('D');
});
expect(result1.current.state).toBe('C');
expect(result2.current.state).toBe('D');
expect(Cookies.get(COOKIE_NAME)).toBe('D');
});
});
48 changes: 16 additions & 32 deletions packages/hooks/src/useCookieState/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import Cookies from 'js-cookie';
import { useState } from 'react';
import useMemoizedFn from '../useMemoizedFn';
import useUpdateEffect from '../useUpdateEffect';
import { isFunction, isString, isUndef } from '../utils';
import { isFunction, isString } from '../utils';

export type State = string | undefined;

Expand All @@ -11,48 +10,33 @@ export interface Options extends Cookies.CookieAttributes {
}

function useCookieState(cookieKey: string, options: Options = {}) {
function getDefaultValue() {
return isFunction(options?.defaultValue) ? options?.defaultValue() : options?.defaultValue;
}

function setStoredValue(newValue: State, newOptions: Cookies.CookieAttributes = {}) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { defaultValue, ...restOptions } = newOptions;

if (isUndef(newValue)) {
Cookies.remove(cookieKey);
} else {
Cookies.set(cookieKey, newValue, restOptions);
}
}

function getStoredValue() {
const [state, setState] = useState<State>(() => {
const cookieValue = Cookies.get(cookieKey);

if (isString(cookieValue)) return cookieValue;

const defaultValue = getDefaultValue();

setStoredValue(defaultValue);

return defaultValue;
}

const [state, setState] = useState<State>(() => getStoredValue());
if (isFunction(options.defaultValue)) {
return options.defaultValue();
}

useUpdateEffect(() => {
setState(getStoredValue());
}, [cookieKey]);
return options.defaultValue;
});

const updateState = useMemoizedFn(
(
newValue: State | ((prevState: State) => State),
newOptions: Cookies.CookieAttributes = {},
) => {
const currentValue = isFunction(newValue) ? newValue(state) : newValue;
const { defaultValue, ...restOptions } = { ...options, ...newOptions };
const value = isFunction(newValue) ? newValue(state) : newValue;

setState(value);

setState(currentValue);
setStoredValue(currentValue, newOptions);
if (value === undefined) {
Cookies.remove(cookieKey);
} else {
Cookies.set(cookieKey, value, restOptions);
}
},
);

Expand Down
12 changes: 5 additions & 7 deletions packages/hooks/src/useCountDown/demo/demo1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ import { useCountDown } from 'ahooks';

export default () => {
const [countdown, formattedRes] = useCountDown({
targetDate: '2022-12-31 24:00:00',
targetDate: `${new Date().getFullYear()}-12-31 23:59:59`,
});
const { days, hours, minutes, seconds, milliseconds } = formattedRes;

return (
<>
<p>
There are {days} days {hours} hours {minutes} minutes {seconds} seconds {milliseconds}{' '}
milliseconds until 2022-12-31 24:00:00
</p>
</>
<p>
There are {days} days {hours} hours {minutes} minutes {seconds} seconds {milliseconds}{' '}
milliseconds until {new Date().getFullYear()}-12-31 23:59:59
</p>
);
};
12 changes: 12 additions & 0 deletions packages/hooks/src/useExternal/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,18 @@ describe('useExternal', () => {
unmount();
expect(document.querySelector('script')).toBeNull();
});
it('should not remove when keepWhenUnused is true', () => {
// https://github.com/alibaba/hooks/discussions/2163
const { result, unmount } = setup('b.js', {
keepWhenUnused: true,
});
const script = document.querySelector('script') as HTMLScriptElement;
act(() => {
fireEvent.load(script);
});
unmount();
expect(result.current).toBe('ready');
});

it('css preload should work in IE Edge', () => {
Object.defineProperty(HTMLLinkElement.prototype, 'hideFocus', {
Expand Down
11 changes: 6 additions & 5 deletions packages/hooks/src/useExternal/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ const status = useExternal(path: string, options?: Options);

### Options

| Params | Description | Type | Default |
| ------ | -------------------------------------------------------------------------------------------------------------------- | ------------------- | ------- |
| type | The type of external resources which need to load, support `js`/`css`, if no type, it will deduced according to path | `string` | - |
| js | Attributes supported by `script` | `HTMLScriptElement` | - |
| css | Attributes supported by `link` | `HTMLStyleElement` | - |
| Params | Description | Type | Default |
| -------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------- | ------- |
| type | The type of external resources which need to load, support `js`/`css`, if no type, it will deduced according to path | `string` | - |
| js | Attributes supported by `script` | `HTMLScriptElement` | - |
| css | Attributes supported by `link` | `HTMLStyleElement` | - |
| keepWhenUnused | Allow resources to remain after they have lost their references | `boolean` | `false` |
5 changes: 4 additions & 1 deletion packages/hooks/src/useExternal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import { useEffect, useRef, useState } from 'react';
type JsOptions = {
type: 'js';
js?: Partial<HTMLScriptElement>;
keepWhenUnused?: boolean;
};

type CssOptions = {
type: 'css';
css?: Partial<HTMLStyleElement>;
keepWhenUnused?: boolean;
};

type DefaultOptions = {
type?: never;
js?: Partial<HTMLScriptElement>;
css?: Partial<HTMLStyleElement>;
keepWhenUnused?: boolean;
};

export type Options = JsOptions | CssOptions | DefaultOptions;
Expand Down Expand Up @@ -138,7 +141,7 @@ const useExternal = (path?: string, options?: Options) => {

EXTERNAL_USED_COUNT[path] -= 1;

if (EXTERNAL_USED_COUNT[path] === 0) {
if (EXTERNAL_USED_COUNT[path] === 0 && !options?.keepWhenUnused) {
ref.current?.remove();
}

Expand Down
11 changes: 6 additions & 5 deletions packages/hooks/src/useExternal/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ const status = useExternal(path: string, options?: Options);

### Options

| 参数 | 说明 | 类型 | 默认值 |
| ---- | ----------------------------------------------------------------- | ------------------- | ------ |
| type | 需引入外部资源的类型,支持 `js`/`css`,如果不传,则根据 path 推导 | `string` | - |
| js | `script` 标签支持的属性 | `HTMLScriptElement` | - |
| css | `link` 标签支持的属性 | `HTMLStyleElement` | - |
| 参数 | 说明 | 类型 | 默认值 |
| -------------- | ----------------------------------------------------------------- | ------------------- | ------- |
| type | 需引入外部资源的类型,支持 `js`/`css`,如果不传,则根据 path 推导 | `string` | - |
| js | `script` 标签支持的属性 | `HTMLScriptElement` | - |
| css | `link` 标签支持的属性 | `HTMLStyleElement` | - |
| keepWhenUnused | 在不持有资源的引用后,仍然保留资源 | `boolean` | `false` |
Loading

0 comments on commit dde3385

Please sign in to comment.