Skip to content

Commit

Permalink
List: avoid useSelect in block render (#57077)
Browse files Browse the repository at this point in the history
  • Loading branch information
ellatrix authored and artemiomorales committed Jan 4, 2024
1 parent c1c8a7d commit 11f9a07
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 231 deletions.
20 changes: 18 additions & 2 deletions packages/block-library/src/list-item/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useBlockProps,
useInnerBlocksProps,
BlockControls,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { isRTL, __ } from '@wordpress/i18n';
import { ToolbarButton } from '@wordpress/components';
Expand All @@ -16,6 +17,7 @@ import {
formatIndent,
} from '@wordpress/icons';
import { useMergeRefs } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
Expand All @@ -32,8 +34,22 @@ import {
import { convertToListItems } from './utils';

export function IndentUI( { clientId } ) {
const [ canIndent, indentListItem ] = useIndentListItem( clientId );
const [ canOutdent, outdentListItem ] = useOutdentListItem( clientId );
const indentListItem = useIndentListItem( clientId );
const outdentListItem = useOutdentListItem();
const { canIndent, canOutdent } = useSelect(
( select ) => {
const { getBlockIndex, getBlockRootClientId, getBlockName } =
select( blockEditorStore );
return {
canIndent: getBlockIndex( clientId ) > 0,
canOutdent:
getBlockName(
getBlockRootClientId( getBlockRootClientId( clientId ) )
) === 'core/list-item',
};
},
[ clientId ]
);

return (
<>
Expand Down
125 changes: 63 additions & 62 deletions packages/block-library/src/list-item/hooks/use-enter.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,71 +19,72 @@ import useOutdentListItem from './use-outdent-list-item';

export default function useEnter( props ) {
const { replaceBlocks, selectionChange } = useDispatch( blockEditorStore );
const { getBlock, getBlockRootClientId, getBlockIndex } =
const { getBlock, getBlockRootClientId, getBlockIndex, getBlockName } =
useSelect( blockEditorStore );
const propsRef = useRef( props );
propsRef.current = props;
const [ canOutdent, outdentListItem ] = useOutdentListItem(
propsRef.current.clientId
);
return useRefEffect(
( element ) => {
function onKeyDown( event ) {
if ( event.defaultPrevented || event.keyCode !== ENTER ) {
return;
}
const { content, clientId } = propsRef.current;
if ( content.length ) {
return;
}
event.preventDefault();
if ( canOutdent ) {
outdentListItem();
return;
}
// Here we are in top level list so we need to split.
const topParentListBlock = getBlock(
getBlockRootClientId( clientId )
);
const blockIndex = getBlockIndex( clientId );
const head = cloneBlock( {
...topParentListBlock,
innerBlocks: topParentListBlock.innerBlocks.slice(
0,
blockIndex
),
} );
const middle = createBlock( getDefaultBlockName() );
// Last list item might contain a `list` block innerBlock
// In that case append remaining innerBlocks blocks.
const after = [
...( topParentListBlock.innerBlocks[ blockIndex ]
.innerBlocks[ 0 ]?.innerBlocks || [] ),
...topParentListBlock.innerBlocks.slice( blockIndex + 1 ),
];
const tail = after.length
? [
cloneBlock( {
...topParentListBlock,
innerBlocks: after,
} ),
]
: [];
replaceBlocks(
topParentListBlock.clientId,
[ head, middle, ...tail ],
1
);
// We manually change the selection here because we are replacing
// a different block than the selected one.
selectionChange( middle.clientId );
const outdentListItem = useOutdentListItem();
return useRefEffect( ( element ) => {
function onKeyDown( event ) {
if ( event.defaultPrevented || event.keyCode !== ENTER ) {
return;
}
const { content, clientId } = propsRef.current;
if ( content.length ) {
return;
}
event.preventDefault();
const canOutdent =
getBlockName(
getBlockRootClientId(
getBlockRootClientId( propsRef.current.clientId )
)
) === 'core/list-item';
if ( canOutdent ) {
outdentListItem();
return;
}
// Here we are in top level list so we need to split.
const topParentListBlock = getBlock(
getBlockRootClientId( clientId )
);
const blockIndex = getBlockIndex( clientId );
const head = cloneBlock( {
...topParentListBlock,
innerBlocks: topParentListBlock.innerBlocks.slice(
0,
blockIndex
),
} );
const middle = createBlock( getDefaultBlockName() );
// Last list item might contain a `list` block innerBlock
// In that case append remaining innerBlocks blocks.
const after = [
...( topParentListBlock.innerBlocks[ blockIndex ]
.innerBlocks[ 0 ]?.innerBlocks || [] ),
...topParentListBlock.innerBlocks.slice( blockIndex + 1 ),
];
const tail = after.length
? [
cloneBlock( {
...topParentListBlock,
innerBlocks: after,
} ),
]
: [];
replaceBlocks(
topParentListBlock.clientId,
[ head, middle, ...tail ],
1
);
// We manually change the selection here because we are replacing
// a different block than the selected one.
selectionChange( middle.clientId );
}

element.addEventListener( 'keydown', onKeyDown );
return () => {
element.removeEventListener( 'keydown', onKeyDown );
};
},
[ canOutdent ]
);
element.addEventListener( 'keydown', onKeyDown );
return () => {
element.removeEventListener( 'keydown', onKeyDown );
};
}, [] );
}
14 changes: 9 additions & 5 deletions packages/block-library/src/list-item/hooks/use-enter.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ import useOutdentListItem from './use-outdent-list-item';

export default function useEnter( props, preventDefault ) {
const { replaceBlocks, selectionChange } = useDispatch( blockEditorStore );
const { getBlock, getBlockRootClientId, getBlockIndex } =
const { getBlock, getBlockRootClientId, getBlockIndex, getBlockName } =
useSelect( blockEditorStore );
const propsRef = useRef( props );
propsRef.current = props;
const [ canOutdent, outdentListItem ] = useOutdentListItem(
propsRef.current.clientId
);
const outdentListItem = useOutdentListItem();

return {
onEnter() {
Expand All @@ -32,7 +30,13 @@ export default function useEnter( props, preventDefault ) {
return;
}
preventDefault.current = true;
if ( canOutdent ) {
if (
getBlockName(
getBlockRootClientId(
getBlockRootClientId( propsRef.current.clientId )
)
) === 'core/list-item'
) {
outdentListItem();
return;
}
Expand Down
96 changes: 43 additions & 53 deletions packages/block-library/src/list-item/hooks/use-indent-list-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
import { createBlock, cloneBlock } from '@wordpress/blocks';

export default function useIndentListItem( clientId ) {
const canIndent = useSelect(
( select ) => select( blockEditorStore ).getBlockIndex( clientId ) > 0,
[ clientId ]
);
const { replaceBlocks, selectionChange, multiSelect } =
useDispatch( blockEditorStore );
const {
Expand All @@ -21,55 +17,49 @@ export default function useIndentListItem( clientId ) {
hasMultiSelection,
getMultiSelectedBlockClientIds,
} = useSelect( blockEditorStore );
return [
canIndent,
useCallback( () => {
const _hasMultiSelection = hasMultiSelection();
const clientIds = _hasMultiSelection
? getMultiSelectedBlockClientIds()
: [ clientId ];
const clonedBlocks = clientIds.map( ( _clientId ) =>
cloneBlock( getBlock( _clientId ) )
);
const previousSiblingId = getPreviousBlockClientId( clientId );
const newListItem = cloneBlock( getBlock( previousSiblingId ) );
// If the sibling has no innerBlocks, create a new `list` block.
if ( ! newListItem.innerBlocks?.length ) {
newListItem.innerBlocks = [ createBlock( 'core/list' ) ];
}
// A list item usually has one `list`, but it's possible to have
// more. So we need to preserve the previous `list` blocks and
// merge the new blocks to the last `list`.
newListItem.innerBlocks[
newListItem.innerBlocks.length - 1
].innerBlocks.push( ...clonedBlocks );
return useCallback( () => {
const _hasMultiSelection = hasMultiSelection();
const clientIds = _hasMultiSelection
? getMultiSelectedBlockClientIds()
: [ clientId ];
const clonedBlocks = clientIds.map( ( _clientId ) =>
cloneBlock( getBlock( _clientId ) )
);
const previousSiblingId = getPreviousBlockClientId( clientId );
const newListItem = cloneBlock( getBlock( previousSiblingId ) );
// If the sibling has no innerBlocks, create a new `list` block.
if ( ! newListItem.innerBlocks?.length ) {
newListItem.innerBlocks = [ createBlock( 'core/list' ) ];
}
// A list item usually has one `list`, but it's possible to have
// more. So we need to preserve the previous `list` blocks and
// merge the new blocks to the last `list`.
newListItem.innerBlocks[
newListItem.innerBlocks.length - 1
].innerBlocks.push( ...clonedBlocks );

// We get the selection start/end here, because when
// we replace blocks, the selection is updated too.
const selectionStart = getSelectionStart();
const selectionEnd = getSelectionEnd();
// Replace the previous sibling of the block being indented and the indented blocks,
// with a new block whose attributes are equal to the ones of the previous sibling and
// whose descendants are the children of the previous sibling, followed by the indented blocks.
replaceBlocks(
[ previousSiblingId, ...clientIds ],
[ newListItem ]
// We get the selection start/end here, because when
// we replace blocks, the selection is updated too.
const selectionStart = getSelectionStart();
const selectionEnd = getSelectionEnd();
// Replace the previous sibling of the block being indented and the indented blocks,
// with a new block whose attributes are equal to the ones of the previous sibling and
// whose descendants are the children of the previous sibling, followed by the indented blocks.
replaceBlocks( [ previousSiblingId, ...clientIds ], [ newListItem ] );
if ( ! _hasMultiSelection ) {
selectionChange(
clonedBlocks[ 0 ].clientId,
selectionEnd.attributeKey,
selectionEnd.clientId === selectionStart.clientId
? selectionStart.offset
: selectionEnd.offset,
selectionEnd.offset
);
} else {
multiSelect(
clonedBlocks[ 0 ].clientId,
clonedBlocks[ clonedBlocks.length - 1 ].clientId
);
if ( ! _hasMultiSelection ) {
selectionChange(
clonedBlocks[ 0 ].clientId,
selectionEnd.attributeKey,
selectionEnd.clientId === selectionStart.clientId
? selectionStart.offset
: selectionEnd.offset,
selectionEnd.offset
);
} else {
multiSelect(
clonedBlocks[ 0 ].clientId,
clonedBlocks[ clonedBlocks.length - 1 ].clientId
);
}
}, [ clientId ] ),
];
}
}, [ clientId ] );
}
2 changes: 1 addition & 1 deletion packages/block-library/src/list-item/hooks/use-merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function useMerge( clientId, onMerge ) {
} = useSelect( blockEditorStore );
const { mergeBlocks, moveBlocksToPosition } =
useDispatch( blockEditorStore );
const [ , outdentListItem ] = useOutdentListItem( clientId );
const outdentListItem = useOutdentListItem();

function getTrailingId( id ) {
const order = getBlockOrder( id );
Expand Down
Loading

0 comments on commit 11f9a07

Please sign in to comment.