Skip to content

Commit

Permalink
feat(hooks): add useControlled State (#1355)
Browse files Browse the repository at this point in the history
Co-authored-by: ZhaoChen <ittisennsinn@gmail.com>
  • Loading branch information
itiiss and itiisennsinn committed Oct 18, 2021
1 parent b90e301 commit 4648d23
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 0 deletions.
102 changes: 102 additions & 0 deletions src/utils/demos/useControlled/useControl.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Subtitle, ArgsTable } from '@storybook/addon-docs/blocks';
import { Meta, Story, Canvas } from '@storybook/addon-docs';
import {
Counter,
UnControlledDemo,
ControlledDemo,
WatchUnControlled,
IgnoreControlledChange,
} from './useControl.stories';

<Meta title="MDX/useControlledState" />

# useControl

## 基础用法

### 使用了 useControll 的组件 Counter

```javascript
export const Counter = ({ value, onChange, defaultValue }) => {
const [state, setState] = useControlledState(value, onChange, defaultValue);
return (
<>
<button type="button" onClick={() => setState(state - 1)}>
-
</button>
<span>{state}</span>
<button type="button" onClick={() => setState(state + 1)}>
+
</button>
</>
);
};
```

<Canvas>
<Counter />
</Canvas>

### 非受控模式,只有 defaultValue

```javascript
const UnControlledDemo = () => <Counter defaultValue={0} />;
```

<Canvas>
<UnControlledDemo />
</Canvas>

### 受控模式,参数有 value 和 onChange

```javascript
const ControlledDemo = () => {
const [value, setValue] = useState(0);
return (
<div>
<input type="number" onChange={(e) => setValue(parseInt(e.target.value, 10))} value={value} />
<Counter value={value} onChange={setValue} />
</div>
);
};
```

<Canvas>
<ControlledDemo />
</Canvas>

### 非受控模式,订阅 defaultValue 的值变化

```javascript
const WatchUnControlled = () => {
const [value, setValue] = useState(0);
return (
<div>
<input type="number" onChange={(e) => setValue(parseInt(e.target.value, 10))} value={value} />
<Counter defaultValue={0} onChange={setValue} />
</div>
);
};
```

<Canvas>
<WatchUnControlled />
</Canvas>

### 受控模式,不提供 onChange,只回显展示

```javascript
const IgnoreControlledChange = () => {
const [value, setValue] = useState(0);
return (
<div>
<input type="number" onChange={(e) => setValue(parseInt(e.target.value, 10))} value={value} />
<Counter value={value} />
</div>
);
};
```

<Canvas>
<IgnoreControlledChange />
</Canvas>
66 changes: 66 additions & 0 deletions src/utils/demos/useControlled/useControl.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { useState } from 'react';
import useControlledState from '../../hooks/useControlled';
import Docs from './useControl.mdx';

interface IProps {
value?: number;
onChange?: (value: number) => void;
defaultValue?: number;
}

export const Counter = ({ value, onChange, defaultValue }: IProps) => {
const [state, setState] = useControlledState(value, onChange, defaultValue);
return (
<>
<button type="button" onClick={() => setState(state - 1)}>
-
</button>
<span>{state}</span>
<button type="button" onClick={() => setState(state + 1)}>
+
</button>
</>
);
};

export const UnControlledDemo = () => <Counter defaultValue={0} />;

export const ControlledDemo = () => {
const [value, setValue] = useState(0);
return (
<div>
<input type="number" onChange={(e) => setValue(parseInt(e.target.value, 10))} value={value} />
<Counter value={value} onChange={setValue} />
</div>
);
};

export const WatchUnControlled = () => {
const [value, setValue] = useState(0);
return (
<div>
<input type="number" onChange={(e) => setValue(parseInt(e.target.value, 10))} value={value} />
<Counter defaultValue={0} onChange={setValue} />
</div>
);
};

export const IgnoreControlledChange = () => {
const [value, setValue] = useState(0);
return (
<div>
<input type="number" onChange={(e) => setValue(parseInt(e.target.value, 10))} value={value} />
<Counter value={value} />
</div>
);
};

export default {
title: 'Hooks/useContrlledState',
component: Counter,
parameters: {
docs: {
page: Docs,
},
},
};
28 changes: 28 additions & 0 deletions src/utils/hooks/useControlled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useState, useEffect, useDebugValue, Dispatch, SetStateAction } from 'react';
import { isUndefined } from 'lodash';

function format<T>([localValue, inputValue]: [T, T]) {
const isControlled = isUndefined(inputValue) ? 'uncontrolled' : 'controlled';
const value = isUndefined(inputValue) ? localValue : inputValue;
return `State is ${isControlled} | Value is ${value}`;
}

const useControlledState = <T>(
value?: T,
onChange?: (value?: T) => void,
defaultValue?: T
): [T | undefined, Dispatch<SetStateAction<T>>] => {
const [localValue, setLocalValue] = useState(() => (isUndefined(value) ? defaultValue : value));

useDebugValue([localValue, value], format);

useEffect(() => {
if (typeof onChange === 'function') {
onChange(localValue);
}
}, [localValue, onChange]);

return [isUndefined(value) ? localValue : value, setLocalValue];
};

export default useControlledState;

1 comment on commit 4648d23

@vercel
Copy link

@vercel vercel bot commented on 4648d23 Oct 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.