Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix <FormDataConsumer> causes its children to flicker #10417

Merged
merged 4 commits into from
Dec 17, 2024
Merged
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
29 changes: 17 additions & 12 deletions packages/ra-core/src/form/FormDataConsumer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useFormContext, FieldValues } from 'react-hook-form';
import get from 'lodash/get';
import { useFormValues } from './useFormValues';
import { useWrappedSource } from '../core';
import { useEvent } from '../util';

/**
* Get the current (edited) value of the record from the form and pass it
Expand Down Expand Up @@ -67,22 +68,26 @@ export const FormDataConsumerView = <
props: Props<TFieldValues>
) => {
const { children, formData, source } = props;
let ret;
const [result, setResult] = React.useState<ReactNode>(null);

const finalSource = useWrappedSource(source || '');
const render = useEvent(children);

// Passes an empty string here as we don't have the children sources and we just want to know if we are in an iterator
const matches = ArraySourceRegex.exec(finalSource);
// Getting the result of the children function in a useEffect allows us to keep a stable reference to is
// with useEvent
React.useEffect(() => {
// Passes an empty string here as we don't have the children sources and we just want to know if we are in an iterator
const matches = ArraySourceRegex.exec(finalSource);
// If we have an index, we are in an iterator like component (such as the SimpleFormIterator)
if (matches) {
const scopedFormData = get(formData, matches[0]);
setResult(render({ formData, scopedFormData }));
} else {
setResult(render({ formData }));
}
}, [finalSource, formData, render]);

// If we have an index, we are in an iterator like component (such as the SimpleFormIterator)
if (matches) {
const scopedFormData = get(formData, matches[0]);
ret = children({ formData, scopedFormData });
} else {
ret = children({ formData });
}

return ret === undefined ? null : ret;
return result;
};

const ArraySourceRegex = new RegExp(/.+\.\d+$/);
Expand Down
95 changes: 95 additions & 0 deletions packages/ra-ui-materialui/src/form/FormDataConsumer.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as React from 'react';
import { FormDataConsumer, required, ResourceContextProvider } from 'ra-core';
import fakeRestDataProvider from 'ra-data-fakerest';
import { AdminContext } from '../AdminContext';
import { AutocompleteInput, ReferenceInput, TextInput } from '../input';
import { SimpleForm } from './SimpleForm';
import { Create } from '../detail';

// We keep this test in ra-ui-materialui because we need heavy components to reproduce the issue https://github.com/marmelab/react-admin/issues/10415
export default { title: 'ra-core/form/FormDataConsumer' };

export const Basic = () => (
<AdminContext dataProvider={dataProvider}>
<ResourceContextProvider value="posts">
<Create>
<SimpleForm>
<TextInput source="title" />
<FormDataConsumer<any>>
{({ formData }) => {
console.log({ formData });
if (!formData.title) {
return null;
}
return (
<ReferenceInput
source="userId"
reference="users"
>
<AutocompleteInput
shouldUnregister
label="User"
optionText={choice =>
`${choice.name} / (${choice.id})`
}
noOptionsText="User doesn't exist"
isRequired
validate={[
required('User is required.'),
]}
/>
</ReferenceInput>
);
}}
</FormDataConsumer>
<TextInput source="body" multiline rows={5} />
</SimpleForm>
</Create>
</ResourceContextProvider>
</AdminContext>
);

const dataProvider = fakeRestDataProvider({
users: [
{
id: 1,
name: 'Leanne Graham',
},
{
id: 2,
name: 'Ervin Howell',
},
{
id: 3,
name: 'Clementine Bauch',
},
{
id: 4,
name: 'Patricia Lebsack',
},
{
id: 5,
name: 'Chelsey Dietrich',
},
{
id: 6,
name: 'Mrs. Dennis Schulist',
},
{
id: 7,
name: 'Kurtis Weissnat',
},
{
id: 8,
name: 'Nicholas Runolfsdottir V',
},
{
id: 9,
name: 'Glenna Reichert',
},
{
id: 10,
name: 'Clementina DuBuque',
},
],
});