fix(cm): nested components never have permission (#20291)

* fix(cm): nested components never have permission

* chore: memoize form inputs for complex forms

* fix(cm): performance regression where selectFromResult was running too often due to arguments
This commit is contained in:
Josh 2024-05-09 15:41:07 +01:00 committed by GitHub
parent 8592bde151
commit 9381bbbca6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 111 additions and 80 deletions

View File

@ -1,4 +1,4 @@
import { forwardRef } from 'react'; import { forwardRef, memo } from 'react';
import { Toggle, useComposedRefs, Field } from '@strapi/design-system'; import { Toggle, useComposedRefs, Field } from '@strapi/design-system';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
@ -40,4 +40,6 @@ const BooleanInput = forwardRef<HTMLInputElement, InputProps>(
} }
); );
export { BooleanInput }; const MemoizedBooleanInput = memo(BooleanInput);
export { MemoizedBooleanInput as BooleanInput };

View File

@ -1,4 +1,4 @@
import { forwardRef } from 'react'; import { forwardRef, memo } from 'react';
import { Checkbox, useComposedRefs, Field } from '@strapi/design-system'; import { Checkbox, useComposedRefs, Field } from '@strapi/design-system';
@ -31,4 +31,6 @@ const CheckboxInput = forwardRef<HTMLInputElement, InputProps>(
} }
); );
export { CheckboxInput }; const MemoizedCheckboxInput = memo(CheckboxInput);
export { MemoizedCheckboxInput as CheckboxInput };

View File

@ -1,4 +1,4 @@
import { forwardRef } from 'react'; import { forwardRef, memo } from 'react';
import { DatePicker, useComposedRefs, Field } from '@strapi/design-system'; import { DatePicker, useComposedRefs, Field } from '@strapi/design-system';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
@ -37,4 +37,6 @@ const DateInput = forwardRef<HTMLInputElement, InputProps>(
} }
); );
export { DateInput }; const MemoizedDateInput = memo(DateInput);
export { MemoizedDateInput as DateInput };

View File

@ -1,4 +1,4 @@
import { forwardRef } from 'react'; import { forwardRef, memo } from 'react';
import { DateTimePicker, useComposedRefs, Field } from '@strapi/design-system'; import { DateTimePicker, useComposedRefs, Field } from '@strapi/design-system';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
@ -37,4 +37,6 @@ const DateTimeInput = forwardRef<HTMLInputElement, InputProps>(
} }
); );
export { DateTimeInput }; const MemoizedDateTimeInput = memo(DateTimeInput);
export { MemoizedDateTimeInput as DateTimeInput };

View File

@ -1,4 +1,4 @@
import { forwardRef } from 'react'; import { forwardRef, memo } from 'react';
import { TextInput, useComposedRefs, Field } from '@strapi/design-system'; import { TextInput, useComposedRefs, Field } from '@strapi/design-system';
@ -7,7 +7,7 @@ import { useField } from '../Form';
import type { StringProps } from './types'; import type { StringProps } from './types';
export const EmailInput = forwardRef<HTMLInputElement, StringProps>( const EmailInput = forwardRef<HTMLInputElement, StringProps>(
({ name, required, label, hint, labelAction, ...props }, ref) => { ({ name, required, label, hint, labelAction, ...props }, ref) => {
const field = useField(name); const field = useField(name);
const fieldRef = useFocusInputField<HTMLInputElement>(name); const fieldRef = useFocusInputField<HTMLInputElement>(name);
@ -32,3 +32,7 @@ export const EmailInput = forwardRef<HTMLInputElement, StringProps>(
); );
} }
); );
const MemoizedEmailInput = memo(EmailInput);
export { MemoizedEmailInput as EmailInput };

View File

@ -1,4 +1,4 @@
import { forwardRef } from 'react'; import { forwardRef, memo } from 'react';
import { SingleSelect, SingleSelectOption, useComposedRefs, Field } from '@strapi/design-system'; import { SingleSelect, SingleSelectOption, useComposedRefs, Field } from '@strapi/design-system';
@ -7,7 +7,7 @@ import { useField } from '../Form';
import { EnumerationProps } from './types'; import { EnumerationProps } from './types';
export const EnumerationInput = forwardRef<HTMLDivElement, EnumerationProps>( const EnumerationInput = forwardRef<HTMLDivElement, EnumerationProps>(
({ name, required, label, hint, labelAction, options = [], ...props }, ref) => { ({ name, required, label, hint, labelAction, options = [], ...props }, ref) => {
const field = useField(name); const field = useField(name);
const fieldRef = useFocusInputField<HTMLDivElement>(name); const fieldRef = useFocusInputField<HTMLDivElement>(name);
@ -39,3 +39,7 @@ export const EnumerationInput = forwardRef<HTMLDivElement, EnumerationProps>(
); );
} }
); );
const MemoizedEnumerationInput = memo(EnumerationInput);
export { MemoizedEnumerationInput as EnumerationInput };

View File

@ -1,4 +1,4 @@
import { forwardRef } from 'react'; import { forwardRef, memo } from 'react';
import { import {
JSONInput as JSONInputImpl, JSONInput as JSONInputImpl,
@ -12,7 +12,7 @@ import { useField } from '../Form';
import { InputProps } from './types'; import { InputProps } from './types';
export const JsonInput = forwardRef<JSONInputRef, InputProps>( const JsonInput = forwardRef<JSONInputRef, InputProps>(
({ name, required, label, hint, labelAction, ...props }, ref) => { ({ name, required, label, hint, labelAction, ...props }, ref) => {
const field = useField(name); const field = useField(name);
const fieldRef = useFocusInputField(name); const fieldRef = useFocusInputField(name);
@ -40,3 +40,7 @@ export const JsonInput = forwardRef<JSONInputRef, InputProps>(
); );
} }
); );
const MemoizedJsonInput = memo(JsonInput);
export { MemoizedJsonInput as JsonInput };

View File

@ -1,4 +1,4 @@
import { forwardRef } from 'react'; import { forwardRef, memo } from 'react';
import { NumberInput, useComposedRefs, Field } from '@strapi/design-system'; import { NumberInput, useComposedRefs, Field } from '@strapi/design-system';
@ -34,4 +34,6 @@ const NumberInputImpl = forwardRef<HTMLInputElement, InputProps>(
} }
); );
export { NumberInputImpl as NumberInput }; const MemoizedNumberInput = memo(NumberInputImpl);
export { MemoizedNumberInput as NumberInput };

View File

@ -1,4 +1,4 @@
import { forwardRef, useState } from 'react'; import { forwardRef, memo, useState } from 'react';
import { TextInput, useComposedRefs, Field } from '@strapi/design-system'; import { TextInput, useComposedRefs, Field } from '@strapi/design-system';
import { Eye, EyeStriked } from '@strapi/icons'; import { Eye, EyeStriked } from '@strapi/icons';
@ -9,7 +9,7 @@ import { useField } from '../Form';
import type { StringProps } from './types'; import type { StringProps } from './types';
export const PasswordInput = forwardRef<HTMLInputElement, StringProps>( const PasswordInput = forwardRef<HTMLInputElement, StringProps>(
({ name, required, label, hint, labelAction, ...props }, ref) => { ({ name, required, label, hint, labelAction, ...props }, ref) => {
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@ -55,3 +55,7 @@ export const PasswordInput = forwardRef<HTMLInputElement, StringProps>(
); );
} }
); );
const MemoizedPasswordInput = memo(PasswordInput);
export { MemoizedPasswordInput as PasswordInput };

View File

@ -93,4 +93,6 @@ const NotSupportedField = forwardRef<any, InputProps>(
} }
); );
export { InputRenderer }; const MemoizedInputRenderer = memo(InputRenderer);
export { MemoizedInputRenderer as InputRenderer };

View File

@ -1,11 +1,11 @@
import { forwardRef } from 'react'; import { forwardRef, memo } from 'react';
import { TextInput, useComposedRefs, Field } from '@strapi/design-system'; import { TextInput, useComposedRefs, Field } from '@strapi/design-system';
import { useFocusInputField } from '../../hooks/useFocusInputField'; import { useFocusInputField } from '../../hooks/useFocusInputField';
import { type InputProps, useField } from '../Form'; import { type InputProps, useField } from '../Form';
export const StringInput = forwardRef<HTMLInputElement, InputProps>( const StringInput = forwardRef<HTMLInputElement, InputProps>(
({ name, required, label, hint, labelAction, ...props }, ref) => { ({ name, required, label, hint, labelAction, ...props }, ref) => {
const field = useField(name); const field = useField(name);
const fieldRef = useFocusInputField<HTMLInputElement>(name); const fieldRef = useFocusInputField<HTMLInputElement>(name);
@ -28,3 +28,7 @@ export const StringInput = forwardRef<HTMLInputElement, InputProps>(
); );
} }
); );
const MemoizedStringInput = memo(StringInput);
export { MemoizedStringInput as StringInput };

View File

@ -1,4 +1,4 @@
import { forwardRef } from 'react'; import { forwardRef, memo } from 'react';
import { Textarea, useComposedRefs, Field } from '@strapi/design-system'; import { Textarea, useComposedRefs, Field } from '@strapi/design-system';
@ -7,7 +7,7 @@ import { useField } from '../Form';
import type { StringProps } from './types'; import type { StringProps } from './types';
export const TextareaInput = forwardRef<HTMLTextAreaElement, StringProps>( const TextareaInput = forwardRef<HTMLTextAreaElement, StringProps>(
({ name, required, label, hint, labelAction, ...props }, ref) => { ({ name, required, label, hint, labelAction, ...props }, ref) => {
const field = useField(name); const field = useField(name);
const fieldRef = useFocusInputField<HTMLTextAreaElement>(name); const fieldRef = useFocusInputField<HTMLTextAreaElement>(name);
@ -30,3 +30,7 @@ export const TextareaInput = forwardRef<HTMLTextAreaElement, StringProps>(
); );
} }
); );
const MemoizedTextareaInput = memo(TextareaInput);
export { MemoizedTextareaInput as TextareaInput };

View File

@ -1,4 +1,4 @@
import { forwardRef } from 'react'; import { forwardRef, memo } from 'react';
import { TimePicker, useComposedRefs, Field } from '@strapi/design-system'; import { TimePicker, useComposedRefs, Field } from '@strapi/design-system';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
@ -36,4 +36,6 @@ const TimeInput = forwardRef<HTMLInputElement, InputProps>(
} }
); );
export { TimeInput }; const MemoizedTimeInput = memo(TimeInput);
export { MemoizedTimeInput as TimeInput };

View File

@ -104,12 +104,9 @@ const DocumentRBAC = ({ children, permissions }: DocumentRBACProps) => {
.filter((field) => field.split('.').length > 1); .filter((field) => field.split('.').length > 1);
if (fieldType === 'component') { if (fieldType === 'component') {
const componentOrDynamicZoneFields = componentFieldNames
// then map to give us the dot separate path as an array
.map((field) => field.split('.'));
// check if the field name is within any of those arrays // check if the field name is within any of those arrays
return componentOrDynamicZoneFields.some((field) => { return componentFieldNames.some((field) => {
return field.includes(fieldName); return field.includes(name.join('.'));
}); });
} }

View File

@ -26,32 +26,25 @@ const useContentTypeSchema = (model?: string) => {
const { toggleNotification } = useNotification(); const { toggleNotification } = useNotification();
const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler(); const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
const { components, contentType, contentTypes, error, isLoading, isFetching } = const { data, error, isLoading, isFetching } = useGetInitialDataQuery(undefined);
useGetInitialDataQuery(undefined, {
selectFromResult: (res) => {
const contentType = res.data?.contentTypes.find((ct) => ct.uid === model);
const componentsByKey = res.data?.components.reduce<ComponentsDictionary>( const { components, contentType, contentTypes } = React.useMemo(() => {
(acc, component) => { const contentType = data?.contentTypes.find((ct) => ct.uid === model);
const componentsByKey = data?.components.reduce<ComponentsDictionary>((acc, component) => {
acc[component.uid] = component; acc[component.uid] = component;
return acc; return acc;
}, }, {});
{}
);
const components = extractContentTypeComponents(contentType?.attributes, componentsByKey); const components = extractContentTypeComponents(contentType?.attributes, componentsByKey);
return { return {
isLoading: res.isLoading,
isFetching: res.isFetching,
error: res.error,
components: Object.keys(components).length === 0 ? undefined : components, components: Object.keys(components).length === 0 ? undefined : components,
contentType, contentType,
contentTypes: res.data?.contentTypes ?? [], contentTypes: data?.contentTypes ?? [],
}; };
}, }, [model, data]);
});
React.useEffect(() => { React.useEffect(() => {
if (error) { if (error) {

View File

@ -38,4 +38,6 @@ const BlocksInput = React.forwardRef<{ focus: () => void }, BlocksInputProps>(
} }
); );
export { BlocksInput }; const MemoizedBlocksInput = React.memo(BlocksInput);
export { MemoizedBlocksInput as BlocksInput };

View File

@ -109,5 +109,7 @@ const ComponentInput = ({
); );
}; };
export { ComponentInput }; const MemoizedComponentInput = React.memo(ComponentInput);
export { MemoizedComponentInput as ComponentInput };
export type { ComponentInputProps }; export type { ComponentInputProps };

View File

@ -1,7 +1,6 @@
import { TextInput } from '@strapi/design-system'; import { Field, TextInput } from '@strapi/design-system';
import { EyeStriked } from '@strapi/icons'; import { EyeStriked } from '@strapi/icons';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { styled } from 'styled-components';
import type { InputProps } from '@strapi/admin/strapi-admin'; import type { InputProps } from '@strapi/admin/strapi-admin';
import type { Schema } from '@strapi/types'; import type { Schema } from '@strapi/types';
@ -19,26 +18,18 @@ const NotAllowedInput = ({ hint, label, required, name }: NotAllowedInputProps)
}); });
return ( return (
<Field.Root id={name} hint={hint} name={name} required={required}>
<Field.Label>{label}</Field.Label>
<TextInput <TextInput
disabled disabled
// @ts-expect-error label _could_ be a ReactNode since it's a child, this should be fixed in the DS.
label={label}
id={name}
hint={hint}
name={name}
placeholder={placeholder} placeholder={placeholder}
required={required} startAction={<EyeStriked fill="neutral600" />}
startAction={<StyledIcon />}
type="text" type="text"
value="" value=""
/> />
<Field.Hint />
</Field.Root>
); );
}; };
const StyledIcon = styled(EyeStriked)`
& > path {
fill: ${({ theme }) => theme.colors.neutral600};
}
`;
export { NotAllowedInput }; export { NotAllowedInput };

View File

@ -1032,5 +1032,7 @@ const RelationItemPlaceholder = () => (
/> />
); );
export { RelationsField as RelationsInput, FlexWrapper, DisconnectButton, LinkEllipsis }; const MemoizedRelationsField = React.memo(RelationsField);
export { MemoizedRelationsField as RelationsInput, FlexWrapper, DisconnectButton, LinkEllipsis };
export type { RelationsFieldProps }; export type { RelationsFieldProps };

View File

@ -322,5 +322,7 @@ const LoadingWrapper = styled<FlexComponent>(Flex)`
animation: ${rotation} 2s infinite linear; animation: ${rotation} 2s infinite linear;
`; `;
export { UIDInput }; const MemoizedUIDInput = React.memo(UIDInput);
export { MemoizedUIDInput as UIDInput };
export type { UIDInputProps }; export type { UIDInputProps };

View File

@ -150,5 +150,7 @@ const Wysiwyg = React.forwardRef<EditorApi, WysiwygProps>(
} }
); );
export { Wysiwyg }; const MemoizedWysiwyg = React.memo(Wysiwyg);
export { MemoizedWysiwyg as Wysiwyg };
export type { WysiwygProps }; export type { WysiwygProps };

View File

@ -1,4 +1,4 @@
import { ReactNode } from 'react'; import { ReactNode, memo } from 'react';
import { import {
useStrapiApp, useStrapiApp,
@ -228,5 +228,7 @@ const getMinMax = (attribute: Schema.Attribute.AnyAttribute) => {
} }
}; };
const MemoizedInputRenderer = memo(InputRenderer);
export type { InputRendererProps }; export type { InputRendererProps };
export { InputRenderer, useFieldHint }; export { MemoizedInputRenderer as InputRenderer, useFieldHint };