Skip to content
Open
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
2 changes: 1 addition & 1 deletion examples/simple/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"react": "^18.3.1",
"react-admin": "^5.11.0",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-hook-form": "^7.62.0",
"react-router": "^6.28.1",
"react-router-dom": "^6.28.1"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/ra-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"jscodeshift": "^0.15.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-hook-form": "^7.62.0",
"react-router": "^6.28.1",
"react-router-dom": "^6.28.1",
"rimraf": "^3.0.2",
Expand All @@ -53,7 +53,7 @@
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"react-hook-form": "^7.53.0",
"react-hook-form": "^7.62.0",
"react-router": "^6.28.1 || ^7.1.1",
"react-router-dom": "^6.28.1 || ^7.1.1"
},
Expand Down
1 change: 1 addition & 0 deletions packages/ra-core/src/core/SourceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type SourceContextValue = {
export const SourceContext = createContext<SourceContextValue | undefined>(
undefined
);
SourceContext.displayName = 'SourceContextProvider';

const defaultContextValue = {
getSource: (source: string) => source,
Expand Down
11 changes: 4 additions & 7 deletions packages/ra-core/src/form/FilterLiveForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ export const FilterLiveForm = (props: FilterLiveFormProps) => {
resolver: finalResolver,
...rest,
});
const { handleSubmit, getValues, reset, watch, formState } = formContext;
const { isValid } = formState;
const { handleSubmit, getValues, reset, watch } = formContext;

// Reapply filterValues when they change externally
useEffect(() => {
Expand All @@ -95,11 +94,9 @@ export const FilterLiveForm = (props: FilterLiveFormProps) => {
}, [JSON.stringify(filterValues), getValues, reset]);

const onSubmit = (values: any): void => {
// Do not call setFilters if the form is invalid
if (!isValid) {
return;
}
setFilters(mergeObjNotArray(filterValues, values));
handleSubmit(newValues =>
setFilters(mergeObjNotArray(filterValues, newValues))
)(values);
};
const debouncedOnSubmit = useDebouncedEvent(onSubmit, debounce || 0);

Expand Down
1 change: 1 addition & 0 deletions packages/ra-core/src/form/groups/FormGroupsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createContext } from 'react';
export const FormGroupsContext = createContext<
FormGroupsContextValue | undefined
>(undefined);
FormGroupsContext.displayName = 'FormGroupsContext';

export type FormGroupSubscriber = () => void;

Expand Down
19 changes: 14 additions & 5 deletions packages/ra-core/src/form/useApplyInputDefaultValues.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useRef } from 'react';
import {
FieldValues,
UseFieldArrayReturn,
Expand Down Expand Up @@ -36,12 +36,21 @@ export const useApplyInputDefaultValues = ({
const finalSource = useWrappedSource(source);

const record = useRecordContext(inputProps);
const { getValues, resetField, formState, reset } = useFormContext();
const { getValues, resetField, reset, subscribe } = useFormContext();
const recordValue = get(record, finalSource);
const formValue = get(getValues(), finalSource);
const { dirtyFields } = formState;
const isDirty = Object.keys(dirtyFields).includes(finalSource);
const isDirty = useRef<boolean | undefined>(undefined);

useEffect(() => {
return subscribe({
formState: { dirtyFields: true },
callback: ({ dirtyFields }) => {
isDirty.current = Object.keys(dirtyFields ?? {}).includes(
finalSource
);
},
});
}, [finalSource, subscribe]);
useEffect(() => {
if (
defaultValue == null ||
Expand All @@ -52,7 +61,7 @@ export const useApplyInputDefaultValues = ({
// We check strictly for undefined to avoid setting default value
// when the field is null
recordValue !== undefined ||
isDirty
isDirty.current === true
) {
return;
}
Expand Down
93 changes: 90 additions & 3 deletions packages/ra-core/src/form/useInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { useForm, FormProvider, useFieldArray } from 'react-hook-form';
import { CoreAdminContext } from '../core';
import { Form } from './Form';
import { InputProps, useInput } from './useInput';
Expand All @@ -7,12 +8,15 @@ export default {
title: 'ra-core/form/useInput',
};

const Input = (props: InputProps) => {
const Input = (props: InputProps & { log?: boolean }) => {
const { label, log } = props;
const { id, field, fieldState } = useInput(props);

if (log) {
console.log(`Input ${id} rendered:`);
}
return (
<label htmlFor={id}>
{id}: <input id={id} {...field} />
{label ?? id}: <input id={id} {...field} />
{fieldState.error && <span>{fieldState.error.message}</span>}
</label>
);
Expand Down Expand Up @@ -86,3 +90,86 @@ DefaultValue.argTypes = {
control: { type: 'select' },
},
};

export const Large = () => {
const [submittedData, setSubmittedData] = React.useState<any>();
const fields = Array.from({ length: 15 }).map((_, index) => (
<Input
key={index}
source={`field${index + 1}`}
label={`field${index + 1}`}
/>
));
return (
<CoreAdminContext>
<Form
onSubmit={data => setSubmittedData(data)}
record={Array.from({ length: 15 }).reduce((acc, _, index) => {
acc[`field${index + 1}`] = `value${index + 1}`;
return acc;
}, {})}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '1em',
marginBottom: '1em',
}}
>
{fields}
</div>
<button type="submit">Submit</button>
</Form>
<pre>{JSON.stringify(submittedData, null, 2)}</pre>
</CoreAdminContext>
);
};

const FieldArray = () => {
const { fields } = useFieldArray({
name: 'arrayField',
});
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '1em' }}>
{fields.map((field, index) => (
<Input key={field.id} source={`arrayField.${index}.name`} log />
))}
</div>
);
};
export const ArrayOfFields = () => {
const formValue = useForm({
defaultValues: {
arrayField: Array.from({ length: 50 }, (_, index) => ({
id: index + 1,
name: `Item ${index + 1}`,
})),
},
});
const [submittedData, setSubmittedData] = React.useState<any>();
return (
<CoreAdminContext>
<FormProvider {...formValue}>
<form
onSubmit={formValue.handleSubmit(data =>
setSubmittedData(data)
)}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '1em',
marginBottom: '1em',
}}
>
<FieldArray />
</div>
<button type="submit">Submit</button>
</form>
</FormProvider>
<pre>{JSON.stringify(submittedData, null, 2)}</pre>
</CoreAdminContext>
);
};
2 changes: 1 addition & 1 deletion packages/ra-input-rich-text/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"ra-ui-materialui": "^5.11.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-hook-form": "^7.62.0",
"rimraf": "^3.0.2",
"tippy.js": "^6.3.7",
"typescript": "^5.1.3"
Expand Down
2 changes: 1 addition & 1 deletion packages/ra-ui-materialui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"ra-language-english": "^5.11.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-hook-form": "^7.62.0",
"react-is": "^18.2.0 || ^19.0.0",
"react-router": "^6.28.1",
"react-router-dom": "^6.28.1",
Expand Down
18 changes: 14 additions & 4 deletions packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { type ReactElement, useEffect } from 'react';
import { type ReactElement, useEffect, useState } from 'react';
import clsx from 'clsx';
import {
isRequired,
Expand All @@ -24,6 +24,7 @@ import {
type ComponentsOverrides,
useThemeProps,
} from '@mui/material';
import get from 'lodash/get';

import { LinearProgress } from '../../layout';
import type { CommonInputProps } from '../CommonInputProps';
Expand Down Expand Up @@ -99,13 +100,24 @@ export const ArrayInput = (inProps: ArrayInputProps) => {
const formGroups = useFormGroups();
const parentSourceContext = useSourceContext();
const finalSource = parentSourceContext.getSource(arraySource);
const [error, setError] = useState<any>();

const sanitizedValidate = Array.isArray(validate)
? composeSyncValidators(validate)
: validate;
const getValidationErrorMessage = useGetValidationErrorMessage();

const { getFieldState, formState, getValues } = useFormContext();
const { getValues, subscribe } = useFormContext();

useEffect(() => {
return subscribe({
formState: { errors: true },
callback: ({ errors }) => {
const error = get(errors ?? {}, finalSource);
setError(error);
},
});
}, [finalSource, subscribe]);

const fieldProps = useFieldArray({
name: finalSource,
Expand Down Expand Up @@ -142,8 +154,6 @@ export const ArrayInput = (inProps: ArrayInputProps) => {
fieldArrayInputControl: fieldProps,
});

const { error } = getFieldState(finalSource, formState);

// The SourceContext will be read by children of ArrayInput to compute their composed source and label
//
// <ArrayInput source="orders" /> => SourceContext is "orders"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Edit } from '../../detail';
import { SimpleForm } from '../../form';
import { ArrayInput } from './ArrayInput';
import { SimpleFormIterator } from './SimpleFormIterator';
import { NumberInput } from '../NumberInput';
import { TextInput } from '../TextInput';
import { AdminContext } from '../../AdminContext';
import { defaultTheme } from '../../theme/defaultTheme';
Expand All @@ -14,6 +15,7 @@ import {
testDataProvider,
useSimpleFormIteratorItem,
} from 'ra-core';
import { AutocompleteInput } from '../AutocompleteInput';

export default { title: 'ra-ui-materialui/input/SimpleFormIterator' };

Expand Down Expand Up @@ -286,3 +288,43 @@ export const WithFormDataConsumer = () => (
</ResourceContextProvider>
</AdminContext>
);

const largeDataProvider = {
getOne: async () => ({
data: {
id: 1,
name: 'Book 1',
authors: Array.from({ length: 100 }, (_, i) => ({
id: i + 1,
first_name: `Author ${i + 1}`,
last_name: `LastName ${i + 1}`,
age: 30 + (i % 20),
})),
},
}),
} as any;

export const Large = () => (
<AdminContext dataProvider={largeDataProvider} defaultTheme="light">
<Edit resource="books" id="1">
<SimpleForm>
<TextInput source="name" />
<ArrayInput source="authors">
<SimpleFormIterator inline>
<TextInput source="first_name" helperText={false} />
<TextInput source="last_name" helperText={false} />
<NumberInput source="age" helperText={false} />
<AutocompleteInput
source="status"
choices={[
{ id: 'active', name: 'Active' },
{ id: 'inactive', name: 'Inactive' },
]}
helperText={false}
/>
</SimpleFormIterator>
</ArrayInput>
</SimpleForm>
</Edit>
</AdminContext>
);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { ReactElement, ReactNode, useMemo } from 'react';
import { ReactElement, ReactNode, useMemo, memo } from 'react';
import { Typography, Stack } from '@mui/material';
import clsx from 'clsx';
import {
Expand All @@ -18,8 +18,8 @@ import { SimpleFormIteratorClasses } from './useSimpleFormIteratorStyles';
import { RemoveItemButton as DefaultRemoveItemButton } from './RemoveItemButton';
import { ReOrderButtons as DefaultReOrderButtons } from './ReOrderButtons';

export const SimpleFormIteratorItem = React.forwardRef(
(props: SimpleFormIteratorItemProps, ref: any) => {
export const SimpleFormIteratorItem = memo(
React.forwardRef((props: SimpleFormIteratorItemProps, ref: any) => {
const {
children,
disabled,
Expand Down Expand Up @@ -141,7 +141,7 @@ export const SimpleFormIteratorItem = React.forwardRef(
</li>
</SimpleFormIteratorItemContext.Provider>
);
}
})
);

export type DisableRemoveFunction = (record: RaRecord) => boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/react-admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"ra-i18n-polyglot": "^5.11.0",
"ra-language-english": "^5.11.0",
"ra-ui-materialui": "^5.11.0",
"react-hook-form": "^7.53.0",
"react-hook-form": "^7.62.0",
"react-router": "^6.28.1 || ^7.1.1",
"react-router-dom": "^6.28.1 || ^7.1.1"
},
Expand Down
Loading
Loading