Skip to content

Commit 0acd866

Browse files
Merge pull request #1404 from creative-commoners/pulls/6/unused-prop
ENH Remove unused prop from ElementEditor
2 parents 6167c9e + effa46f commit 0acd866

3 files changed

Lines changed: 86 additions & 11 deletions

File tree

client/dist/js/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/components/ElementEditor/ElementEditor.js

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,27 @@ const ElementEditor = ({
2626
elementTypes,
2727
allowedElements,
2828
sharedObject,
29-
isLoading,
3029
actions,
3130
forceRefetchElements,
3231
}) => {
3332
const [dragging, setDragging] = useState(false);
3433
const [elements, setElements] = useState(null);
35-
// isLoadingState is unused, possibly because isLoading prop is being incorrectly used
36-
// setIsLoadingState is still used to update the state internally
37-
// eslint-disable-next-line no-unused-vars
38-
const [isLoadingState, setIsLoadingState] = useState(true);
34+
const [loading, setLoading] = useState(true);
35+
const [showLoadingIndicator, setShowLoadingIndicator] = useState(false);
3936

4037
/**
4138
* Make an API call to read all elements endpoint (areaID)
4239
*/
4340
const fetchElements = (doSetLoadingState = true) => {
4441
if (doSetLoadingState) {
45-
setIsLoadingState(true);
42+
setLoading(true);
4643
}
4744
const url = `${getConfig().controllerLink.replace(/\/$/, '')}/api/readElements/${areaId}`;
4845
return backend.get(url)
4946
.then(async (response) => {
5047
const responseJson = await response.json();
5148
setElements(responseJson);
52-
setIsLoadingState(false);
49+
setLoading(false);
5350
// refresh preview
5451
const preview = window.jQuery('.cms-preview');
5552
if (preview) {
@@ -59,7 +56,7 @@ const ElementEditor = ({
5956
})
6057
.catch(async (err) => {
6158
setElements([]);
62-
setIsLoadingState(false);
59+
setLoading(false);
6360
const message = await getJsonErrorMessage(err);
6461
actions.toasts.error(message);
6562
actions.editor.reloadComplete(areaId);
@@ -111,6 +108,27 @@ const ElementEditor = ({
111108
setElements(sortedElements);
112109
};
113110

111+
/**
112+
* Delay loading indicator to prevent FOUT
113+
*/
114+
useEffect(() => {
115+
let timeoutId;
116+
if (!loading) {
117+
setShowLoadingIndicator(false);
118+
} else {
119+
timeoutId = setTimeout(() => {
120+
setShowLoadingIndicator(true);
121+
}, 300);
122+
}
123+
// Cleanup timeout - this will run on unmount, and whenever dependency changes, i.e. loading state changes
124+
//
125+
return () => {
126+
if (timeoutId) {
127+
clearTimeout(timeoutId);
128+
}
129+
};
130+
}, [loading]);
131+
114132
/**
115133
* Fetch elements if null or forcing a refetch, e.g. moving a block from one elemental area to another
116134
* in the same parent DataObject record.
@@ -153,7 +171,7 @@ const ElementEditor = ({
153171
dragging={dragging}
154172
sharedObject={sharedObject}
155173
elements={elements}
156-
isLoading={isLoading}
174+
isLoading={showLoadingIndicator}
157175
/>
158176
</ElementEditorContext.Provider>
159177
</div>;

client/src/components/ElementEditor/tests/ElementEditor-test.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const mockEvent = {
104104
function makeProps(obj = {}) {
105105
return {
106106
ToolbarComponent: ({ elementTypes }) => <div data-testid="test-toolbar" data-elementtypes={elementTypes.map(type => type.class).join(',')} />,
107-
ListComponent: ({ elements, onDragEnd }) => <div className="test-list">
107+
ListComponent: ({ elements, onDragEnd, isLoading }) => <div className="test-list" data-is-loading={isLoading}>
108108
{elements.map(element => <div id={`Element${element.id}`} key={element.id} onClick={() => onDragEnd(mockEvent)}>{element.title}</div>)}
109109
</div>,
110110
areaId: 8,
@@ -550,3 +550,60 @@ test('ElementEditor preserves dragging state during sort request', async () => {
550550
await screen.findByTestId('test-toolbar');
551551
expect(timesReloaded).toBe(2);
552552
});
553+
554+
test('ElementEditor does not show loading indicator for fast operations (< 300ms)', async () => {
555+
const { container } = render(<ElementEditor {...makeProps()}/>);
556+
// Immediately resolve - operation completes in < 300ms
557+
resolveBackendGet(createJsonResponse());
558+
await screen.findByTestId('test-toolbar');
559+
const listComponent = container.querySelector('.test-list');
560+
expect(listComponent.getAttribute('data-is-loading')).toBe('false');
561+
});
562+
563+
test('ElementEditor shows loading indicator for slow operations (> 300ms)', async () => {
564+
jest.useFakeTimers();
565+
const { container } = render(<ElementEditor {...makeProps()}/>);
566+
// Wait for 300ms
567+
jest.advanceTimersByTime(300);
568+
const listComponent = container.querySelector('.test-list');
569+
// Should show loading after 300ms
570+
expect(listComponent).toBeNull(); // Still returning null since elements is null
571+
// Resolve the backend call
572+
resolveBackendGet(createJsonResponse());
573+
await screen.findByTestId('test-toolbar');
574+
jest.useRealTimers();
575+
});
576+
577+
test('ElementEditor clears loading indicator timeout on fast completion', async () => {
578+
jest.useFakeTimers();
579+
const { container } = render(<ElementEditor {...makeProps()}/>);
580+
// Advance time by 100ms (less than 300ms debounce)
581+
jest.advanceTimersByTime(100);
582+
// Resolve quickly
583+
resolveBackendGet(createJsonResponse());
584+
// Advance past debounce time
585+
jest.advanceTimersByTime(250);
586+
await screen.findByTestId('test-toolbar');
587+
const listComponent = container.querySelector('.test-list');
588+
// Should not show loading since it completed before 300ms
589+
expect(listComponent.getAttribute('data-is-loading')).toBe('false');
590+
jest.useRealTimers();
591+
});
592+
593+
test('ElementEditor debounced loading works on refetch', async () => {
594+
jest.useFakeTimers();
595+
const { rerender, container } = render(<ElementEditor {...makeProps()}/>);
596+
resolveBackendGet(createJsonResponse());
597+
jest.advanceTimersByTime(300);
598+
await screen.findByTestId('test-toolbar');
599+
expect(timesReloaded).toBe(1);
600+
// Trigger refetch
601+
rerender(<ElementEditor {...makeProps({ forceRefetchElements: true })}/>);
602+
// Complete quickly
603+
resolveBackendGet(createJsonResponse());
604+
jest.advanceTimersByTime(100);
605+
await screen.findByTestId('test-toolbar');
606+
const listComponent = container.querySelector('.test-list');
607+
expect(listComponent.getAttribute('data-is-loading')).toBe('false');
608+
jest.useRealTimers();
609+
});

0 commit comments

Comments
 (0)