Skip to content

Commit

Permalink
Merge pull request #6533 from marmelab/fix-boolean-input-initialValue
Browse files Browse the repository at this point in the history
Fix BooleanInput initialValue overrides existing value from record
  • Loading branch information
fzaninotto authored Aug 27, 2021
2 parents c3c483b + 0f7d0e9 commit 038f44c
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 4 deletions.
95 changes: 94 additions & 1 deletion packages/ra-core/src/form/useInput.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import { FunctionComponent, ReactElement } from 'react';
import { render, fireEvent } from '@testing-library/react';
import { Form } from 'react-final-form';
import { Form, useFormState } from 'react-final-form';
import FormWithRedirect from './FormWithRedirect';
import useInput, { InputProps } from './useInput';
import { required } from './validate';
Expand Down Expand Up @@ -235,4 +235,97 @@ describe('useInput', () => {
);
expect(queryByDisplayValue('99')).toBeNull();
});

const BooleanInput = ({
source,
initialValue,
}: {
source: string;
initialValue?: boolean;
}) => (
<Input
source={source}
initialValue={initialValue}
type="checkbox"
resource="posts"
>
{() => <BooleanInputValue source={source} />}
</Input>
);

const BooleanInputValue = ({ source }) => {
const values = useFormState().values;
return (
<>
{typeof values[source] === 'undefined'
? 'undefined'
: values[source]
? 'true'
: 'false'}
</>
);
};

it('does not change the value if the field is of type checkbox and has no value', () => {
const { queryByText } = renderWithRedux(
<FormWithRedirect
onSubmit={jest.fn()}
record={{ id: 1 }}
render={() => <BooleanInput source="is_published" />}
/>
);
expect(queryByText('undefined')).not.toBeNull();
});

it('applies the initialValue true when the field is of type checkbox and has no value', () => {
const { queryByText } = renderWithRedux(
<FormWithRedirect
onSubmit={jest.fn()}
record={{ id: 1 }}
render={() => (
<BooleanInput source="is_published" initialValue={true} />
)}
/>
);
expect(queryByText('true')).not.toBeNull();
});

it('applies the initialValue false when the field is of type checkbox and has no value', () => {
const { queryByText } = renderWithRedux(
<FormWithRedirect
onSubmit={jest.fn()}
record={{ id: 1 }}
render={() => (
<BooleanInput source="is_published" initialValue={false} />
)}
/>
);
expect(queryByText('false')).not.toBeNull();
});

it('does not apply the initialValue true when the field is of type checkbox and has a value', () => {
const { queryByText } = renderWithRedux(
<FormWithRedirect
onSubmit={jest.fn()}
record={{ id: 1, is_published: false }}
render={() => (
<BooleanInput source="is_published" initialValue={true} />
)}
/>
);
expect(queryByText('false')).not.toBeNull();
});

it('does not apply the initialValue false when the field is of type checkbox and has a value', () => {
const { queryByText } = renderWithRedux(
<FormWithRedirect
onSubmit={jest.fn()}
record={{ id: 1, is_published: true }}
render={() => (
<BooleanInput source="is_published" initialValue={false} />
)}
/>
);
expect(queryByText('true')).not.toBeNull();
});
});
21 changes: 18 additions & 3 deletions packages/ra-core/src/form/useInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,27 +111,42 @@ const useInput = ({
[onFocus, customOnFocus]
);

// Every time the record changes and didn't include a value for this field
const form = useForm();
const recordId = record?.id;
// Every time the record changes and doesn't include a value for this field,
// reset the field value to the initialValue (or defaultValue)
useEffect(() => {
if (input.value != null && input.value !== '') {
if (
typeof input.checked !== 'undefined' || // checkbox that has a value from record
(input.value != null && input.value !== '') // any other input that has a value from record
) {
// no need to apply a default value
return;
}

// Apply the default value if provided
// We use a change here which will make the form dirty but this is expected
// and identical to what FinalForm does (https://final-form.org/docs/final-form/types/FieldConfig#defaultvalue)
if (defaultValue != null) {
form.change(source, defaultValue);
}

// apply initial value if provided
if (initialValue != null) {
form.batch(() => {
form.change(source, initialValue);
form.resetFieldState(source);
});
}
}, [recordId, input.value, defaultValue, initialValue, source, form]);
}, [
recordId,
input.value,
input.checked,
defaultValue,
initialValue,
source,
form,
]);

// If there is an input prop, this input has already been enhanced by final-form
// This is required in for inputs used inside other inputs (such as the SelectInput inside a ReferenceInput)
Expand Down
35 changes: 35 additions & 0 deletions packages/ra-ui-materialui/src/input/BooleanInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,41 @@ describe('<BooleanInput />', () => {
expect(input.checked).toBe(false);
});

it('should be checked if the value is undefined and initialValue is true', () => {
const { getByLabelText } = render(
<Form
onSubmit={jest.fn}
render={() => (
<BooleanInput {...defaultProps} initialValue={true} />
)}
/>
);

const input = getByLabelText(
'resources.posts.fields.isPublished'
) as HTMLInputElement;

expect(input.checked).toBe(true);
});

it('should be checked if the value is true and initialValue is false', () => {
const { getByLabelText } = render(
<Form
onSubmit={jest.fn}
initialValues={{ isPublished: true }}
render={() => (
<BooleanInput {...defaultProps} initialValue={false} />
)}
/>
);

const input = getByLabelText(
'resources.posts.fields.isPublished'
) as HTMLInputElement;

expect(input.checked).toBe(true);
});

it('should update on click', async () => {
const { getByLabelText } = render(
<Form
Expand Down

0 comments on commit 038f44c

Please sign in to comment.