Skip to content

Commit

Permalink
Merge branch 'master' into fix/useReactive
Browse files Browse the repository at this point in the history
  • Loading branch information
awmleer authored Dec 28, 2020
2 parents 8e5fd80 + 8a073cd commit 19d56f0
Show file tree
Hide file tree
Showing 17 changed files with 239 additions and 138 deletions.
19 changes: 0 additions & 19 deletions .github/workflows/preview.yml

This file was deleted.

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": "2.9.2",
"version": "2.9.3",
"description": "react hooks library",
"keywords": [
"ahooks",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { act, renderHook } from '@testing-library/react-hooks';
import useStorageState, { IFuncUpdater } from '../index';
import { IFuncUpdater, createUseStorageState } from '../index';

class TestStorage implements Storage {
[name: string]: any;
Expand Down Expand Up @@ -48,10 +48,11 @@ interface StorageStateProps<T> {
describe('useStorageState', () => {
const setUp = <T>(props: StorageStateProps<T>) => {
const storage = new TestStorage();
const useStorageState = createUseStorageState(storage);

return renderHook(
({ key, defaultValue }: StorageStateProps<T>) => {
const [state, setState] = useStorageState(storage, key, defaultValue);
const [state, setState] = useStorageState(key, defaultValue);

return { state, setState };
},
Expand All @@ -62,7 +63,7 @@ describe('useStorageState', () => {
};

it('should be defined', () => {
expect(useStorageState);
expect(createUseStorageState);
});

it('should get defaultValue for a given key', () => {
Expand Down
71 changes: 71 additions & 0 deletions packages/hooks/src/createUseStorageState/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useState, useCallback } from 'react';
import useUpdateEffect from '../useUpdateEffect';

export interface IFuncUpdater<T> {
(previousState?: T): T;
}

export interface IFuncStorage {
(): Storage;
}

export type StorageStateResult<T> = [T | undefined, (value?: T | IFuncUpdater<T>) => void];

function isFunction<T>(obj: any): obj is T {
return typeof obj === 'function';
}

export function createUseStorageState(nullishStorage: Storage | null) {
function useStorageState<T>(
key: string,
defaultValue?: T | IFuncUpdater<T>,
): StorageStateResult<T> {
const storage = nullishStorage as Storage;
const [state, setState] = useState<T | undefined>(() => getStoredValue());
useUpdateEffect(() => {
setState(getStoredValue());
}, [key]);

function getStoredValue() {
const raw = storage.getItem(key);
if (raw) {
try {
return JSON.parse(raw);
} catch (e) {}
}
if (isFunction<IFuncUpdater<T>>(defaultValue)) {
return defaultValue();
}
return defaultValue;
}

const updateState = useCallback(
(value?: T | IFuncUpdater<T>) => {
if (typeof value === 'undefined') {
storage.removeItem(key);
setState(undefined);
} else if (isFunction<IFuncUpdater<T>>(value)) {
const previousState = getStoredValue();
const currentState = value(previousState);
storage.setItem(key, JSON.stringify(currentState));
setState(currentState);
} else {
storage.setItem(key, JSON.stringify(value));
setState(value);
}
},
[key],
);

return [state, updateState];
}
if (!nullishStorage) {
return function (defaultValue: any) {
return [
isFunction<IFuncUpdater<any>>(defaultValue) ? defaultValue() : defaultValue,
() => {},
];
} as typeof useStorageState;
}
return useStorageState;
}
2 changes: 1 addition & 1 deletion packages/hooks/src/useClickAway/demo/demo3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import React, { useState, useRef } from 'react';
import useClickAway from '../index';
import { useClickAway } from 'ahooks';

export default () => {
const [counter, setCounter] = useState(0);
Expand Down
2 changes: 1 addition & 1 deletion packages/hooks/src/useCountDown/demo/demo1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import React from 'react';
import useCountDown from '../index';
import { useCountDown } from 'ahooks';

export default () => {
const [countdown, setTargetDate, formattedRes] = useCountDown({
Expand Down
2 changes: 1 addition & 1 deletion packages/hooks/src/useCountDown/demo/demo2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import React from 'react';
import useCountDown from '../index';
import { useCountDown } from 'ahooks';

export default () => {
const onEnd = () => {
Expand Down
36 changes: 36 additions & 0 deletions packages/hooks/src/useDebounceEffect/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,40 @@ describe('useDebounceEffect', () => {
expect(mockCleanUp.mock.calls.length).toEqual(1);
});
});

it('should cancel timeout on unmount', async () => {
const mockEffect = jest.fn(() => {});
const mockCleanUp = jest.fn(() => {});

const hook = renderHook(
(props) =>
useDebounceEffect(
() => {
mockEffect();
return () => {
mockCleanUp();
};
},
[props],
{ wait: 200 },
),
{ initialProps: 0 },
);

await act(async () => {
expect(mockEffect.mock.calls.length).toEqual(0);
expect(mockCleanUp.mock.calls.length).toEqual(0);

hook.rerender(1);
await sleep(50);
expect(mockEffect.mock.calls.length).toEqual(0);
expect(mockCleanUp.mock.calls.length).toEqual(0);

await sleep(300);
hook.unmount();

expect(mockEffect.mock.calls.length).toEqual(1);
expect(mockCleanUp.mock.calls.length).toEqual(1);
});
});
});
5 changes: 4 additions & 1 deletion packages/hooks/src/useDebounceEffect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, EffectCallback, DependencyList, useState } from 'react';
import { DebounceOptions } from '../useDebounce/debounceOptions';
import useDebounceFn from '../useDebounceFn';
import useUpdateEffect from '../useUpdateEffect';
import useUnmount from '../useUnmount';

function useDebounceEffect(
effect: EffectCallback,
Expand All @@ -10,14 +11,16 @@ function useDebounceEffect(
) {
const [flag, setFlag] = useState({});

const { run } = useDebounceFn(() => {
const { run, cancel } = useDebounceFn(() => {
setFlag({});
}, options);

useEffect(() => {
return run();
}, deps);

useUnmount(cancel);

useUpdateEffect(effect, [flag]);
}

Expand Down
17 changes: 4 additions & 13 deletions packages/hooks/src/useLocalStorageState/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import useStorageState from '../useStorageState';
import { createUseStorageState } from '../createUseStorageState';

function useLocalStorageState<T = undefined>(
key: string,
): [T | undefined, (value?: T | ((previousState?: T) => T)) => void];

function useLocalStorageState<T>(
key: string,
defaultValue: T | (() => T),
): [T, (value?: T | ((previousState: T) => T)) => void];

function useLocalStorageState<T>(key: string, defaultValue?: T | (() => T)) {
return useStorageState(() => localStorage, key, defaultValue);
}
const useLocalStorageState = createUseStorageState(
typeof window === 'object' ? window.localStorage : null,
);

export default useLocalStorageState;
6 changes: 2 additions & 4 deletions packages/hooks/src/usePersistFn/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { useRef, useEffect } from 'react';
import { useRef } from 'react';

export type noop = (...args: any[]) => any;

function usePersistFn<T extends noop>(fn: T) {
const fnRef = useRef<T>(fn);
useEffect(() => {
fnRef.current = fn;
}, [fn]);
fnRef.current = fn;

const persistFn = useRef<T>();
if (!persistFn.current) {
Expand Down
71 changes: 68 additions & 3 deletions packages/hooks/src/useReactive/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,73 @@
import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import Demo from '../demo';
import { sleep } from '../../utils/testingHelpers';
import { act } from 'react-dom/test-utils';
import useReactive from '../';

const Demo = () => {
let state = useReactive({
count: 0,
val: {
val1: {
val2: '',
},
},
arr: [1],
});

return (
<div>
<p>
counter state.count:<span role="addCount">{state.count}</span>
</p>

<button role="addCountBtn" onClick={() => (state.count += 1)}>
state.count++
</button>
<button role="subCountBtn" style={{ marginLeft: '50px' }} onClick={() => (state.count -= 1)}>
state.count--
</button>
<br />
<br />
<p>
state.arr: <span role="test-array">{JSON.stringify(state.arr)}</span>
</p>
<button style={{ marginRight: '10px' }} onClick={() => state.arr.push(1)} role="pushbtn">
push
</button>
<button style={{ marginRight: '10px' }} onClick={() => state.arr.pop()} role="popbtn">
pop
</button>
<button style={{ marginRight: '10px' }} onClick={() => state.arr.shift()} role="shiftbtn">
shift
</button>
<button
style={{ marginRight: '10px' }}
role="unshiftbtn"
onClick={() => state.arr.unshift(2)}
>
unshift
</button>
<button style={{ marginRight: '10px' }} role="reverse" onClick={() => state.arr.reverse()}>
reverse
</button>
<button style={{ marginRight: '10px' }} role="sort" onClick={() => state.arr.sort()}>
sort
</button>
<br />
<br />
<p>nested structure</p>
<p role="inputVal1">{state.val.val1.val2}</p>
<input
role="input1"
style={{ width: 220, borderWidth: 1 }}
type="text"
onChange={(e) => {
state.val.val1.val2 = e.target.value;
}}
/>
</div>
);
};

describe('test useReactive feature', () => {
it('test count ', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/hooks/src/useReactive/demo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import useReactive from '../index';
import { useReactive } from 'ahooks';

export default () => {
let state = useReactive({
Expand Down
17 changes: 4 additions & 13 deletions packages/hooks/src/useSessionStorageState/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import useStorageState from '../useStorageState';
import { createUseStorageState } from '../createUseStorageState';

function useSessionStorageState<T = undefined>(
key: string,
): [T | undefined, (value?: T | ((previousState?: T) => T)) => void];

function useSessionStorageState<T>(
key: string,
defaultValue: T | (() => T),
): [T, (value?: T | ((previousState: T) => T)) => void];

function useSessionStorageState<T>(key: string, defaultValue?: T | (() => T)) {
return useStorageState(() => sessionStorage, key, defaultValue);
}
const useSessionStorageState = createUseStorageState(
typeof window === 'object' ? window.sessionStorage : null,
);

export default useSessionStorageState;
Loading

0 comments on commit 19d56f0

Please sign in to comment.