diff --git a/packages/core/admin/admin/src/content-manager/components/NonRepeatableComponent/index.js b/packages/core/admin/admin/src/content-manager/components/NonRepeatableComponent/index.js
index 721472d1ab..06c0031153 100644
--- a/packages/core/admin/admin/src/content-manager/components/NonRepeatableComponent/index.js
+++ b/packages/core/admin/admin/src/content-manager/components/NonRepeatableComponent/index.js
@@ -9,6 +9,7 @@ import { Stack } from '@strapi/design-system/Stack';
import { useContentTypeLayout } from '../../hooks';
import FieldComponent from '../FieldComponent';
import Inputs from '../Inputs';
+import useLazyComponents from '../../hooks/useLazyComponents';
const NonRepeatableComponent = ({ componentUid, isFromDynamicZone, isNested, name }) => {
const { getComponentLayout } = useContentTypeLayout();
@@ -18,6 +19,8 @@ const NonRepeatableComponent = ({ componentUid, isFromDynamicZone, isNested, nam
);
const fields = componentLayoutData.layouts.edit;
+ const { lazyComponentStore } = useLazyComponents();
+
return (
);
diff --git a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/components/Component.js b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/components/Component.js
index 48401aa348..ac91892f46 100644
--- a/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/components/Component.js
+++ b/packages/core/admin/admin/src/content-manager/components/RepeatableComponent/components/Component.js
@@ -28,6 +28,7 @@ import Inputs from '../../Inputs';
import FieldComponent from '../../FieldComponent';
import Preview from './Preview';
+import useLazyComponents from '../../../hooks/useLazyComponents';
const CustomIconButton = styled(IconButton)`
background-color: transparent;
@@ -124,6 +125,8 @@ const DraggedItem = ({
const composedAccordionRefs = composeRefs(accordionRef, dragRef);
const composedBoxRefs = composeRefs(boxRef, dropRef);
+ const { lazyComponentStore } = useLazyComponents();
+
return (
{isDragging ? (
@@ -212,6 +215,7 @@ const DraggedItem = ({
metadatas={metadatas}
queryInfos={queryInfos}
size={size}
+ customFieldInputs={lazyComponentStore}
/>
);
diff --git a/packages/core/admin/admin/src/content-manager/hooks/useLazyComponents/index.js b/packages/core/admin/admin/src/content-manager/hooks/useLazyComponents/index.js
index 1b3ddf0462..59bd9f4904 100644
--- a/packages/core/admin/admin/src/content-manager/hooks/useLazyComponents/index.js
+++ b/packages/core/admin/admin/src/content-manager/hooks/useLazyComponents/index.js
@@ -1,44 +1,69 @@
-import { useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import { useCustomFields } from '@strapi/helper-plugin';
+const componentStore = new Map();
+
/**
* @description
* A hook to lazy load custom field components
* @param {Array.} componentUids - The uids to look up components
* @returns object
*/
-const useLazyComponents = (componentUids) => {
- const [lazyComponentStore, setLazyComponentStore] = useState({});
- const [loading, setLoading] = useState(true);
+const useLazyComponents = (componentUids = []) => {
+ const [lazyComponentStore, setLazyComponentStore] = useState(Object.fromEntries(componentStore));
+ const [loading, setLoading] = useState(() => {
+ if (componentStore.size === 0 && componentUids.length > 0) {
+ return true;
+ }
+
+ return false;
+ });
const customFieldsRegistry = useCustomFields();
useEffect(() => {
+ const setStore = (store) => {
+ setLazyComponentStore(store);
+ setLoading(false);
+ };
+
const lazyLoadComponents = async (uids, components) => {
const modules = await Promise.all(components);
uids.forEach((uid, index) => {
- if (!Object.keys(lazyComponentStore).includes(uid)) {
- setLazyComponentStore({ ...lazyComponentStore, [uid]: modules[index].default });
- }
+ componentStore.set(uid, modules[index].default);
});
+
+ setStore(Object.fromEntries(componentStore));
};
- if (componentUids.length) {
- const componentPromises = componentUids.map((uid) => {
+ if (componentUids.length && loading) {
+ /**
+ * These uids are not in the component store therefore we need to get the components
+ */
+ const newUids = componentUids.filter((uid) => !componentStore.get(uid));
+
+ const componentPromises = newUids.map((uid) => {
const customField = customFieldsRegistry.get(uid);
return customField.components.Input();
});
- lazyLoadComponents(componentUids, componentPromises);
+ if (componentPromises.length > 0) {
+ lazyLoadComponents(newUids, componentPromises);
+ }
}
+ }, [componentUids, customFieldsRegistry, loading]);
- if (componentUids.length === Object.keys(lazyComponentStore).length) {
- setLoading(false);
- }
- }, [componentUids, customFieldsRegistry, loading, lazyComponentStore]);
+ /**
+ * Wrap this in a callback so it can be used in
+ * effects to cleanup the cached store if required
+ */
+ const cleanup = useCallback(() => {
+ componentStore.clear();
+ setLazyComponentStore({});
+ }, []);
- return { isLazyLoading: loading, lazyComponentStore };
+ return { isLazyLoading: loading, lazyComponentStore, cleanup };
};
export default useLazyComponents;
diff --git a/packages/core/admin/admin/src/content-manager/hooks/useLazyComponents/tests/useLazyComponents.test.js b/packages/core/admin/admin/src/content-manager/hooks/useLazyComponents/tests/useLazyComponents.test.js
index e5effa9a15..db825503be 100644
--- a/packages/core/admin/admin/src/content-manager/hooks/useLazyComponents/tests/useLazyComponents.test.js
+++ b/packages/core/admin/admin/src/content-manager/hooks/useLazyComponents/tests/useLazyComponents.test.js
@@ -26,25 +26,50 @@ jest.mock('@strapi/helper-plugin', () => ({
}));
describe('useLazyComponents', () => {
+ let cleanup;
+
+ afterEach(() => {
+ if (typeof cleanup === 'function') {
+ cleanup();
+ cleanup = undefined;
+ }
+ });
+
it('lazy loads the components', async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
+ const { result, waitFor } = renderHook(() => useLazyComponents(['plugin::test.test']));
+
+ cleanup = result.current.cleanup;
+
+ expect(result.current.isLazyLoading).toEqual(true);
+ expect(result.current.lazyComponentStore).toEqual({});
+
+ await waitFor(() => expect(result.current.isLazyLoading).toEqual(false));
+
+ expect(result.current.lazyComponentStore['plugin::test.test']).toBeDefined();
+ });
+
+ test('assuming the store has been initialised before hand, other hooks called should be able to access the global cache', async () => {
+ const { result: initialResult, waitFor } = renderHook(() =>
useLazyComponents(['plugin::test.test'])
);
- expect(result.current).toEqual({ isLazyLoading: true, lazyComponentStore: {} });
-
- await waitForNextUpdate();
-
- expect(JSON.stringify(result.current)).toEqual(
- JSON.stringify({
- isLazyLoading: false,
- lazyComponentStore: { 'plugin::test.test': jest.fn() },
- })
+ await waitFor(() =>
+ expect(initialResult.current.lazyComponentStore['plugin::test.test']).toBeDefined()
);
+
+ const { result: actualResult, waitFor: secondWaitFor } = renderHook(() => useLazyComponents());
+
+ cleanup = actualResult.current.cleanup;
+
+ await secondWaitFor(() => expect(actualResult.current.isLazyLoading).toBe(false));
+
+ expect(actualResult.current.lazyComponentStore['plugin::test.test']).toBeDefined();
});
- it('handles no components to load', async () => {
+
+ test('given there are no components to load it should not be loading and the store should be empty', async () => {
const { result } = renderHook(() => useLazyComponents([]));
- expect(result.current).toEqual({ isLazyLoading: false, lazyComponentStore: {} });
+ expect(result.current.isLazyLoading).toEqual(false);
+ expect(result.current.lazyComponentStore).toEqual({});
});
});