Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/demo/value-form-event.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## ValueFromEvent


<code src="../examples/value-form-event.tsx"></code>
197 changes: 197 additions & 0 deletions docs/examples/value-form-event.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import Form, { Field } from 'rc-field-form';
import React from 'react';
import Input from './components/Input';

const Divider = (props: React.PropsWithChildren) => (
<div
style={{
background: '#999',
color: "#fff",
textAlign: 'center',
}}
>
<span>🔽🔽🔽</span>
{props.children}
<span>🔽🔽🔽</span>
</div>
)

interface CustomValue {
timestamp: number,
date: Date,
formatted: string,
}

type FormData = {
// Input
text?: string; // default

// ========== 🔽 event.target.checked
checkbox?: boolean;
radio?: boolean;
// ========== 🔽 event.target.valueAsNumber
number?: number;
range?: number;
// ========== 🔽 event.target.files
file?: FileList;

// ========== 🔽 event.target.value(default)!!!
password?: string;
search?: string;
email?: string;
url?: string;
tel?: string;
date?: number;
time?: string;
dateTimeLocal?: string;
week?: string;
month?: string;
color?: string;

// Select
select?: string;
// Textarea
textarea?: string;

// Custom
custom?: CustomValue;
};


function App() {
const [form] = Form.useForm();

const initialValues: FormData = {
url: 'https://github.com/react-component/field-form',
email: 'wxh16144@users.noreply.github.com',
}

return (
<Form
form={form}
preserve={false}
initialValues={initialValues}
onFinish={values => {
console.log('Finish:', values);
}}
onFieldsChange={fields => {
console.error('fields:', fields);
}}
>
<Field<FormData> name="text">
<Input type="text" placeholder='Text' />
</Field>


<Divider>event.target.checked</Divider>

<Field<FormData> name="checkbox">
<Input type="checkbox" placeholder='Checkbox' />
</Field>

<Field<FormData> name="radio">
<Input type="radio" placeholder='Radio' />
</Field>

<Divider>event.target.valueAsNumber</Divider>

<Field<FormData> name="number">
<Input type="number" placeholder='Number' />
</Field>

<Field<FormData> name="range">
<Input type="range" placeholder='Range' />
</Field>

<Divider>event.target.files</Divider>

<Field<FormData> name="file">
<Input type="file" placeholder='File' />
</Field>

<Divider>event.target.value <span style={{ color: 'red' }}>default</span>!!!</Divider>

<Field<FormData> name="password">
<Input type="password" placeholder='Password' />
</Field>

<Field<FormData> name="search">
<Input type="search" placeholder='Search' />
</Field>

<Field<FormData> name="email">
<Input type="email" placeholder='Email' />
</Field>

<Field<FormData> name="url">
<Input type="url" placeholder='Url' />
</Field>

<Field<FormData> name="tel">
<Input type="tel" placeholder='Tel' />
</Field>

<Field<FormData> name="date">
<Input type="date" placeholder='Date' />
</Field>

<Field<FormData> name="dateTimeLocal">
<Input type="datetime-local" placeholder='DatetimeLocal' />
</Field>

<Field<FormData> name="time">
<Input type="time" placeholder='Time' />
</Field>

<Field<FormData> name="week">
<Input type="week" placeholder='Week' />
</Field>

<Field<FormData> name="month">
<Input type="month" placeholder='Month' />
</Field>

<Field<FormData> name="color">
<Input type="color" placeholder='Color' />
</Field>

{/* Select */}
<Field<FormData> name="select">
<select style={{ width: 180 }}>
<option value="1">1</option>
<option value="2">2</option>
</select>
</Field>

<br />

{/* Textarea */}
<Field<FormData> name="textarea">
<textarea placeholder="Textarea" />
</Field>

<Divider>Custom</Divider>

<Field<FormData>
name="custom"
getValueFromEvent={(...[event]) => {
return {
timestamp: event.target.valueAsNumber,
date: event.target.valueAsDate,
formatted: event.target.value,
} as CustomValue
}}
getValueProps={(value: CustomValue) => ({
value: value?.formatted
})}
>
<Input type="datetime-local" placeholder='DatetimeLocal' />
</Field>

<br />
<button type="submit">Submit</button>
</Form>
);
}

export default App
53 changes: 51 additions & 2 deletions src/utils/valueUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,57 @@ export function isSimilar(source: SimilarObject, target: SimilarObject) {

export function defaultGetValueFromEvent(valuePropName: string, ...args: EventArgs) {
const event = args[0];
if (event && event.target && typeof event.target === 'object' && valuePropName in event.target) {
return (event.target as HTMLInputElement)[valuePropName];

/**
* `target` is the element that triggered the event (e.g., the user clicked on)
* `currentTarget` is the element that the event listener is attached to.
*/
const nodeName = (event?.target?.nodeName ?? '').toLowerCase();

if (nodeName === 'input') {
const type = (event.target.type ?? 'text').toLowerCase();

if (['checkbox', 'radio'].includes(type)) {
return event.target.checked;
}

// `datetime` Obsolete
if (['number', 'range'].includes(type)) {
// https://caniuse.com/?search=valueAsNumber, support IE11+
return event.target.valueAsNumber ?? event.target.value;
}

/**
* Problems with backfilling the data collected, so it is not processed here
* @see https://devlog.willcodefor.beer/pages/use-valueasnumber-and-valueasdate-on-inputs/
* `datetime` Obsolete
*/
// if (['date', 'datetime-local'].includes(type)) {
// const _value = {
// timestamp: event.target.valueAsNumber,
// date: event.target.valueAsDate,
// formatted: event.target.value,
// }
// }

if (type === 'file') {
return event.target.files;
}

/**
* text password search email url week month tel color time
* [submit, reset, button, image, hidden] ?? i dont care :)
*/
// return event.target.value; // valuePropName default is 'value'
}

// because `valuePropName` default is `value`
// if (['textarea', 'select'].includes(nodeName)) {
// return event.target.value;
// }

if (typeof event?.target === 'object' && valuePropName in event.target) {
return event.target[valuePropName];
}

return event;
Expand Down
17 changes: 10 additions & 7 deletions tests/legacy/dynamic-binding.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ describe('legacy.dynamic-binding', () => {

rerender(<Test mode />);

expect(getInput(container, '#text')?.value).toBe('456');
expect(form.current?.getFieldValue('name')).toBe('456');
expect(getInput(container, '#text')?.value).toBe("456");
expect(form.current?.getFieldValue('name')).toBe(456);
const values = await form.current?.validateFields();
expect(values.name).toBe('456');
expect(values.name).toBe(456);
expect(typeof values.name).toBe('number');
});

// [Legacy] We do not remove value in Field Form
Expand Down Expand Up @@ -141,9 +142,10 @@ describe('legacy.dynamic-binding', () => {
rerender(<Test mode />);

expect(getInput(container, '#text')?.value).toBe('456');
expect(form.current?.getFieldValue(['name', 'xxx'])).toBe('456');
expect(form.current?.getFieldValue(['name', 'xxx'])).toBe(456);
const values = await form.current?.validateFields();
expect(values.name.xxx).toBe('456');
expect(values.name.xxx).toBe(456);
expect(typeof values.name.xxx).toBe('number');
});

it('input with different keys', async () => {
Expand Down Expand Up @@ -178,10 +180,11 @@ describe('legacy.dynamic-binding', () => {
rerender(<Test mode />);

expect(getInput(container, '#text')?.value).toBe('456');
expect(form.current?.getFieldValue('name')).toBe('456');
expect(form.current?.getFieldValue('name')).toBe(456);

const values = await form.current?.validateFields();
expect(values.name).toBe('456');
expect(values.name).toBe(456);
expect(typeof values.name).toBe('number');
});

it('submit without removed fields', async () => {
Expand Down