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 Unnecessary Renders of Blocks in Edit Mode #6639

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
331 changes: 192 additions & 139 deletions packages/volto/src/components/manage/Blocks/Block/BlocksForm.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useCallback, useRef } from 'react';
import { useIntl } from 'react-intl';
import cloneDeep from 'lodash/cloneDeep';
import map from 'lodash/map';
Expand Down Expand Up @@ -70,6 +70,10 @@ const BlocksForm = (props) => {
}, []);

const blockList = getBlocks(properties);
const propertiesRef = useRef(properties);
useEffect(() => {
propertiesRef.current = properties;
}, [properties]);

const dispatch = useDispatch();
const intl = useIntl();
Expand All @@ -87,165 +91,214 @@ const BlocksForm = (props) => {
disableKeys: !isMainForm,
});

const handleKeyDown = (
e,
index,
block,
node,
{
disableEnter = false,
disableArrowUp = false,
disableArrowDown = false,
} = {},
) => {
const isMultipleSelection = e.shiftKey;
if (e.key === 'ArrowUp' && !disableArrowUp) {
onFocusPreviousBlock(block, node, isMultipleSelection);
e.preventDefault();
}
if (e.key === 'ArrowDown' && !disableArrowDown) {
onFocusNextBlock(block, node, isMultipleSelection);
e.preventDefault();
}
if (e.key === 'Enter' && !disableEnter) {
if (!disableAddBlockOnEnterKey) {
onSelectBlock(onAddBlock(config.settings.defaultBlockType, index + 1));
}
e.preventDefault();
}
};

const onFocusPreviousBlock = (
currentBlock,
blockNode,
isMultipleSelection,
) => {
const prev = previousBlockId(properties, currentBlock);
if (prev === null) return;

blockNode.blur();
const onFocusPreviousBlock = useCallback(
(currentBlock, blockNode, isMultipleSelection) => {
const prev = previousBlockId(propertiesRef.current, currentBlock);
if (prev === null) return;

onSelectBlock(prev, isMultipleSelection);
};

const onFocusNextBlock = (currentBlock, blockNode, isMultipleSelection) => {
const next = nextBlockId(properties, currentBlock);
if (next === null) return;

blockNode.blur();

onSelectBlock(next, isMultipleSelection);
};
blockNode.blur();
onSelectBlock(prev, isMultipleSelection);
},
[onSelectBlock],
);

const onMutateBlock = (id, value) => {
const newFormData = mutateBlock(properties, id, value, {}, intl);
onChangeFormData(newFormData);
};
const onFocusNextBlock = useCallback(
(currentBlock, blockNode, isMultipleSelection) => {
const next = nextBlockId(propertiesRef.current, currentBlock);
if (next === null) return;

const onInsertBlock = (id, value, current) => {
const [newId, newFormData] = insertBlock(
properties,
id,
value,
current,
config.experimental.addBlockButton.enabled ? 1 : 0,
{},
intl,
);
blockNode.blur();
onSelectBlock(next, isMultipleSelection);
},
[onSelectBlock],
);

const blocksFieldname = getBlocksFieldname(newFormData);
const blockData = newFormData[blocksFieldname][newId];
newFormData[blocksFieldname][newId] = applyBlockDefaults({
data: blockData,
intl,
metadata,
properties,
});

onChangeFormData(newFormData);
return newId;
};
const onMutateBlock = useCallback(
(id, value) => {
const newFormData = mutateBlock(
propertiesRef.current,
id,
value,
{},
intl,
);
onChangeFormData(newFormData);
},
[onChangeFormData, intl],
);

const onAddBlock = (type, index) => {
if (editable) {
const [id, newFormData] = addBlock(properties, type, index, {}, intl);
const onInsertBlock = useCallback(
(id, value, current) => {
const [newId, newFormData] = insertBlock(
propertiesRef.current,
id,
value,
current,
config.experimental.addBlockButton.enabled ? 1 : 0,
{},
intl,
);
const blocksFieldname = getBlocksFieldname(newFormData);
const blockData = newFormData[blocksFieldname][id];
newFormData[blocksFieldname][id] = applyBlockDefaults({
const blockData = newFormData[blocksFieldname][newId];
newFormData[blocksFieldname][newId] = applyBlockDefaults({
data: blockData,
intl,
metadata,
properties,
properties: propertiesRef.current,
});

onChangeFormData(newFormData);
return id;
}
};
return newId;
},
[onChangeFormData, intl, metadata], // Dependencies
);

const onChangeBlock = (id, value) => {
const newFormData = changeBlock(properties, id, value);
onChangeFormData(newFormData);
};
const onAddBlock = useCallback(
(type, index) => {
if (editable) {
const [id, newFormData] = addBlock(
propertiesRef.current,
type,
index,
{},
intl,
);
const blocksFieldname = getBlocksFieldname(newFormData);
const blockData = newFormData[blocksFieldname][id];
newFormData[blocksFieldname][id] = applyBlockDefaults({
data: blockData,
intl,
metadata,
properties: propertiesRef.current,
});
onChangeFormData(newFormData);
return id;
}
},
[editable, intl, metadata, onChangeFormData],
);

const onDeleteBlock = (id, selectPrev) => {
const previous = previousBlockId(properties, id);
const onChangeBlock = useCallback(
(id, value) => {
const newFormData = changeBlock(propertiesRef.current, id, value);
onChangeFormData(newFormData);
},
[onChangeFormData], // Dependencies
);

const newFormData = deleteBlock(properties, id, intl);
onChangeFormData(newFormData);
const onDeleteBlock = useCallback(
(id, selectPrev) => {
const previous = previousBlockId(propertiesRef.current, id);
const newFormData = deleteBlock(propertiesRef.current, id, intl);
onChangeFormData(newFormData);

onSelectBlock(selectPrev ? previous : null);
};
onSelectBlock(selectPrev ? previous : null);
},
[onChangeFormData, intl, onSelectBlock],
);

const onMoveBlock = (dragIndex, hoverIndex) => {
const newFormData = moveBlock(properties, dragIndex, hoverIndex);
onChangeFormData(newFormData);
};
const onMoveBlock = useCallback(
(dragIndex, hoverIndex) => {
const newFormData = moveBlock(
propertiesRef.current,
dragIndex,
hoverIndex,
);
onChangeFormData(newFormData);
},
[onChangeFormData], // Dependencies
);

const onMoveBlockEnhanced = ({ source, destination }) => {
const newFormData = moveBlockEnhanced(cloneDeep(properties), {
source,
destination,
});
const blocksFieldname = getBlocksFieldname(newFormData);
const blocksLayoutFieldname = getBlocksLayoutFieldname(newFormData);
let error = false;

const allowedBlocks = Object.keys(blocksConfig);

map(newFormData[blocksLayoutFieldname].items, (id) => {
const block = newFormData[blocksFieldname][id];
if (!allowedBlocks.includes(block['@type'])) {
error = true;
}
if (Array.isArray(block[blocksLayoutFieldname]?.items)) {
const size = block[blocksLayoutFieldname].items.length;
const allowedSubBlocks = [
...(blocksConfig[block['@type']].allowedBlocks || allowedBlocks),
'empty',
] || ['empty'];
if (size < 1 || size > (blocksConfig[block['@type']].maxLength || 4)) {
const onMoveBlockEnhanced = useCallback(
({ source, destination }) => {
const newFormData = moveBlockEnhanced(cloneDeep(propertiesRef.current), {
source,
destination,
});
const blocksFieldname = getBlocksFieldname(newFormData);
const blocksLayoutFieldname = getBlocksLayoutFieldname(newFormData);
let error = false;

const allowedBlocks = Object.keys(blocksConfig);

map(newFormData[blocksLayoutFieldname].items, (id) => {
const block = newFormData[blocksFieldname][id];
if (!allowedBlocks.includes(block['@type'])) {
error = true;
}
map(block[blocksLayoutFieldname].items, (subId) => {
const subBlock = block[blocksFieldname][subId];
if (!allowedSubBlocks.includes(subBlock['@type'])) {
if (Array.isArray(block[blocksLayoutFieldname]?.items)) {
const size = block[blocksLayoutFieldname].items.length;
const allowedSubBlocks = [
...(blocksConfig[block['@type']].allowedBlocks || allowedBlocks),
'empty',
] || ['empty'];
if (
size < 1 ||
size > (blocksConfig[block['@type']].maxLength || 4)
) {
error = true;
}
});
map(block[blocksLayoutFieldname].items, (subId) => {
const subBlock = block[blocksFieldname][subId];
if (!allowedSubBlocks.includes(subBlock['@type'])) {
error = true;
}
});
}
});

if (!error) {
onChangeFormData(newFormData);
dispatch(
setUIState({
selected: null,
multiSelected: [],
gridSelected: null,
}),
);
}
});
},
[onChangeFormData, blocksConfig, dispatch],
);

if (!error) {
onChangeFormData(newFormData);
dispatch(
setUIState({
selected: null,
multiSelected: [],
gridSelected: null,
}),
);
}
};
const handleKeyDown = useCallback(
(
e,
index,
block,
node,
{
disableEnter = false,
disableArrowUp = false,
disableArrowDown = false,
} = {},
) => {
const isMultipleSelection = e.shiftKey;
if (e.key === 'ArrowUp' && !disableArrowUp) {
onFocusPreviousBlock(block, node, isMultipleSelection);
e.preventDefault();
}
if (e.key === 'ArrowDown' && !disableArrowDown) {
onFocusNextBlock(block, node, isMultipleSelection);
e.preventDefault();
}
if (e.key === 'Enter' && !disableEnter) {
if (!disableAddBlockOnEnterKey) {
onSelectBlock(
onAddBlock(config.settings.defaultBlockType, index + 1),
);
}
e.preventDefault();
}
},
[
disableAddBlockOnEnterKey,
onAddBlock,
onFocusNextBlock,
onFocusPreviousBlock,
onSelectBlock,
],
);

const defaultBlockWrapper = ({ draginfo }, editBlock, blockProps) => (
<EditBlockWrapper draginfo={draginfo} blockProps={blockProps}>
Expand Down Expand Up @@ -345,7 +398,7 @@ const BlocksForm = (props) => {
onSelectBlock,
pathname,
metadata,
properties,
properties: propertiesRef,
contentType: type,
navRoot,
blocksConfig,
Expand All @@ -356,7 +409,7 @@ const BlocksForm = (props) => {
showBlockChooser: selectedBlock === childId,
detached: isContainer,
// Properties to pass to the BlocksForm to match the View ones
content: properties,
content: propertiesRef,
history,
location,
token,
Expand Down
Loading
Loading