Skip to content

Commit

Permalink
feat: implement useFunctionalState
Browse files Browse the repository at this point in the history
close #530
  • Loading branch information
xobotyi committed Jun 30, 2022
1 parent 834ea58 commit bfede03
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ Coming from `react-use`? Check out our
— Tracks a numeric value and offers functions for manipulating it.
- [**`useDebouncedState`**](https://react-hookz.github.io/web/?path=/docs/state-usedebouncedstate--example)
— Like `useSafeState` but its state setter is debounced.
- [**`useFunctionalState`**](https://react-hookz.github.io/web/?path=/docs/state-usefunctionalstate--page)
— Like `useState` but instead of raw state, state getter returned.
- [**`useList`**](https://react-hookz.github.io/web/?path=/docs/state-uselist--example)
— Tracks a list and offers functions for manipulating it.
- [**`useMap`**](https://react-hookz.github.io/web/?path=/docs/state-usemap--example) — Tracks the
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export { useIntervalEffect } from './useIntervalEffect/useIntervalEffect';

// State
export { useDebouncedState } from './useDebouncedState/useDebouncedState';
export { useFunctionalState } from './useFunctionalState/useFunctionalState';
export { useList } from './useList/useList';
export { useMap } from './useMap/useMap';
export { useMediatedState } from './useMediatedState/useMediatedState';
Expand Down Expand Up @@ -58,6 +59,7 @@ export { useVibrate } from './useVibrate/useVibrate';

// Miscellaneous
export { useSyncedRef } from './useSyncedRef/useSyncedRef';
export { useHookableRef, HookableRefHandler } from './useHookableRef/useHookableRef';

// SideEffect
export { useLocalStorageValue } from './useLocalStorageValue/useLocalStorageValue';
Expand Down Expand Up @@ -109,5 +111,3 @@ export { useWindowSize, WindowSize } from './useWindowSize/useWindowSize';
export { truthyAndArrayPredicate, truthyOrArrayPredicate } from './util/const';

export { IEffectCallback, IEffectHook } from './util/misc';

export { useHookableRef } from './useHookableRef/useHookableRef';
19 changes: 19 additions & 0 deletions src/useFunctionalState/__docs__/story.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Meta } from '@storybook/addon-docs/blocks';

<Meta title="State/useFunctionalState" />

# useFunctionalState

Like `useState` but instead of raw state, state getter returned. `useSafeState` is used underneath.

## Reference

```ts
export function useFunctionalState<S>(
initialState: S | (() => S)
): [() => S, React.Dispatch<React.SetStateAction<S>>];
export function useFunctionalState<S = undefined>(): [
() => S | undefined,
React.Dispatch<React.SetStateAction<S | undefined>>
];
```
31 changes: 31 additions & 0 deletions src/useFunctionalState/__tests__/dom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { act, renderHook } from '@testing-library/react-hooks/dom';
import { useFunctionalState } from '../..';

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

it('should render', () => {
const { result } = renderHook(() => useFunctionalState());
expect(result.error).toBeUndefined();
});

it('should return proper values', () => {
const { result } = renderHook(() => useFunctionalState(1));
expect(result.current[1]).toBeInstanceOf(Function);
expect(result.current[0]).toBeInstanceOf(Function);
});

it('should return state getter', () => {
const { result } = renderHook(() => useFunctionalState(1));

expect(result.current[0]()).toBe(1);

act(() => {
result.current[1](2);
});

expect(result.current[0]()).toBe(2);
});
});
19 changes: 19 additions & 0 deletions src/useFunctionalState/__tests__/ssr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { renderHook } from '@testing-library/react-hooks/server';
import { useFunctionalState } from '../..';

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

it('should render', () => {
const { result } = renderHook(() => useFunctionalState());
expect(result.error).toBeUndefined();
});

it('should return proper values', () => {
const { result } = renderHook(() => useFunctionalState(1));
expect(result.current[1]).toBeInstanceOf(Function);
expect(result.current[0]).toBeInstanceOf(Function);
});
});
25 changes: 25 additions & 0 deletions src/useFunctionalState/useFunctionalState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Dispatch, SetStateAction, useCallback } from 'react';
import { useSafeState } from '../useSafeState/useSafeState';
import { useSyncedRef } from '../useSyncedRef/useSyncedRef';

export function useFunctionalState<S>(
initialState: S | (() => S)
): [() => S, Dispatch<SetStateAction<S>>];
export function useFunctionalState<S = undefined>(): [
() => S | undefined,
Dispatch<SetStateAction<S | undefined>>
];

/**
* Like `useState` but instead of raw state, state getter returned. `useSafeState` is
* used underneath.
*/
export function useFunctionalState<S>(
initialState?: S | (() => S)
): [() => S | undefined, Dispatch<SetStateAction<S | undefined>>] {
const [state, setState] = useSafeState(initialState);
const stateRef = useSyncedRef(state);

// eslint-disable-next-line react-hooks/exhaustive-deps
return [useCallback(() => stateRef.current, []), setState];
}
4 changes: 2 additions & 2 deletions src/useSafeState/__docs__/story.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ action if component is unmounted, otherwise it is the same hook as common `useSt
#### Example

Sadly we can't provide an example since this documentation built in `production` mode and warning
are only shown in `development` mode.
Sadly we can't provide an example since this documentation built in `production` mode and
warnings are only shown in `development` mode.

## Reference

Expand Down
2 changes: 1 addition & 1 deletion src/useSafeState/__tests__/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('useSafeState', () => {
expect(result.error).toBeUndefined();
});

it('should not call', () => {
it('should not cause state change after component unmount', () => {
const consoleSpy = jest.spyOn(console, 'error');
consoleSpy.mockImplementationOnce(() => {});

Expand Down

0 comments on commit bfede03

Please sign in to comment.