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

useSelect: normalise getting selectors for callbacks #31078

Merged
merged 4 commits into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -48,40 +48,6 @@ function isWindows() {
return window.navigator.platform.indexOf( 'Win' ) > -1;
}

function selector( select ) {
const {
getSelectedBlockClientId,
getMultiSelectedBlocksEndClientId,
getPreviousBlockClientId,
getNextBlockClientId,
hasBlockMovingClientId,
getBlockIndex,
getBlockRootClientId,
getClientIdsOfDescendants,
canInsertBlockType,
getBlockName,
} = select( blockEditorStore );

const selectedBlockClientId = getSelectedBlockClientId();
const selectionEndClientId = getMultiSelectedBlocksEndClientId();

return {
selectedBlockClientId,
selectionBeforeEndClientId: getPreviousBlockClientId(
selectionEndClientId || selectedBlockClientId
),
selectionAfterEndClientId: getNextBlockClientId(
selectionEndClientId || selectedBlockClientId
),
hasBlockMovingClientId,
getBlockIndex,
getBlockRootClientId,
getClientIdsOfDescendants,
canInsertBlockType,
getBlockName,
};
}

/**
* Block selection button component, displaying the label of the block. If the block
* descends from a root block, a button is displayed enabling the user to select
Expand Down Expand Up @@ -137,11 +103,31 @@ function BlockSelectionButton( { clientId, rootClientId, blockElement } ) {
selectedBlockClientId,
selectionBeforeEndClientId,
selectionAfterEndClientId,
} = useSelect( ( select ) => {
const {
getSelectedBlockClientId,
getMultiSelectedBlocksEndClientId,
getPreviousBlockClientId,
getNextBlockClientId,
} = select( blockEditorStore );
const _selectedBlockClientId = getSelectedBlockClientId();
const selectionEndClientId = getMultiSelectedBlocksEndClientId();
return {
selectedBlockClientId: _selectedBlockClientId,
selectionBeforeEndClientId: getPreviousBlockClientId(
selectionEndClientId || _selectedBlockClientId
),
selectionAfterEndClientId: getNextBlockClientId(
selectionEndClientId || _selectedBlockClientId
),
};
}, [] );
const {
hasBlockMovingClientId,
getBlockIndex,
getBlockRootClientId,
getClientIdsOfDescendants,
} = useSelect( selector, [] );
} = useSelect( blockEditorStore );
const {
selectBlock,
clearSelectedBlock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,27 +263,25 @@ export default function useInsertionPoint( ref ) {
isInserterVisible,
selectedClientId,
selectedRootClientId,
getBlockListSettings,
} = useSelect( ( select ) => {
const {
isMultiSelecting: _isMultiSelecting,
isBlockInsertionPointVisible,
getBlockInsertionPoint,
getBlockOrder,
getBlockListSettings: _getBlockListSettings,
} = select( blockEditorStore );

const insertionPoint = getBlockInsertionPoint();
const order = getBlockOrder( insertionPoint.rootClientId );

return {
getBlockListSettings: _getBlockListSettings,
isMultiSelecting: _isMultiSelecting(),
isInserterVisible: isBlockInsertionPointVisible(),
selectedClientId: order[ insertionPoint.index - 1 ],
selectedRootClientId: insertionPoint.rootClientId,
};
}, [] );
const { getBlockListSettings } = useSelect( blockEditorStore );

const onMouseMove = useCallback(
( event ) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,16 +201,7 @@ export default function useBlockNavigationDropZone() {
getBlockCount,
getDraggedBlockClientIds,
canInsertBlocks,
} = useSelect( ( select ) => {
const selectors = select( blockEditorStore );
return {
canInsertBlocks: selectors.canInsertBlocks,
getBlockRootClientId: selectors.getBlockRootClientId,
getBlockIndex: selectors.getBlockIndex,
getBlockCount: selectors.getBlockCount,
getDraggedBlockClientIds: selectors.getDraggedBlockClientIds,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Especially here, the benefits are clear.

};
}, [] );
} = useSelect( blockEditorStore );
const [ target, setTarget ] = useState();
const { rootClientId: targetRootClientId, blockIndex: targetBlockIndex } =
target || {};
Expand Down
12 changes: 3 additions & 9 deletions packages/block-editor/src/components/copy-handler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,8 @@ import { getPasteEventData } from '../../utils/get-paste-event-data';
import { store as blockEditorStore } from '../../store';

export function useNotifyCopy() {
const { getBlockName } = useSelect(
( select ) => select( blockEditorStore ),
[]
);
const { getBlockType } = useSelect(
( select ) => select( blocksStore ),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here too. We're returning the entire selector list.

[]
);
const { getBlockName } = useSelect( blockEditorStore );
const { getBlockType } = useSelect( blocksStore );
const { createSuccessNotice } = useDispatch( noticesStore );

return useCallback( ( eventType, selectedBlockClientIds ) => {
Expand Down Expand Up @@ -84,7 +78,7 @@ export function useClipboardHandler() {
getSelectedBlockClientIds,
hasMultiSelection,
getSettings,
} = useSelect( ( select ) => select( blockEditorStore ), [] );
} = useSelect( blockEditorStore );
const { flashBlock, removeBlocks, replaceBlocks } = useDispatch(
blockEditorStore
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,8 @@ export default function useInnerBlockTemplateSync(
templateLock,
templateInsertUpdatesSelection
) {
const getSelectedBlocksInitialCaretPosition = useSelect(
( select ) =>
select( blockEditorStore ).getSelectedBlocksInitialCaretPosition,
[]
const { getSelectedBlocksInitialCaretPosition } = useSelect(
blockEditorStore
);
const { replaceInnerBlocks } = useDispatch( blockEditorStore );
const innerBlocks = useSelect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,10 @@ function useInsertionPoint( {
onSelect,
shouldFocusBlock = true,
} ) {
const {
destinationRootClientId,
destinationIndex,
getSelectedBlock,
} = useSelect(
const { getSelectedBlock } = useSelect( blockEditorStore );
const { destinationRootClientId, destinationIndex } = useSelect(
( select ) => {
const {
getSelectedBlock: _getSelectedBlock,
getBlockIndex,
getBlockOrder,
getBlockInsertionPoint,
Expand Down Expand Up @@ -90,7 +86,6 @@ function useInsertionPoint( {
}

return {
getSelectedBlock: _getSelectedBlock,
destinationRootClientId: _destinationRootClientId,
destinationIndex: _destinationIndex,
};
Expand Down
18 changes: 5 additions & 13 deletions packages/block-editor/src/components/inserter/menu.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,11 @@ function InserterMenu( {
insertDefaultBlock,
} = useDispatch( blockEditorStore );

const {
items,
destinationRootClientId,
getBlockOrder,
getBlockCount,
canInsertBlockType,
} = useSelect( ( select ) => {
const { items, destinationRootClientId } = useSelect( ( select ) => {
const {
getInserterItems,
getBlockRootClientId,
getBlockSelectionEnd,
...selectBlockEditorStore
} = select( blockEditorStore );

let targetRootClientId = rootClientId;
Expand All @@ -78,13 +71,12 @@ function InserterMenu( {
return {
items: getInserterItems( targetRootClientId ),
destinationRootClientId: targetRootClientId,
getBlockOrder: selectBlockEditorStore.getBlockOrder,
getBlockCount: selectBlockEditorStore.getBlockCount,
canInsertBlockType: selectBlockEditorStore.canInsertBlockType,
};
} );

const { getBlockType } = useSelect( ( select ) => select( blocksStore ) );
const { getBlockOrder, getBlockCount, canInsertBlockType } = useSelect(
blockEditorStore
);
const { getBlockType } = useSelect( blocksStore );

useEffect( () => {
// Show/Hide insertion point on Mount/Dismount
Expand Down
22 changes: 5 additions & 17 deletions packages/block-editor/src/components/use-on-block-drop/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,27 +212,15 @@ export function onHTMLDrop(
* @return {Object} An object that contains the event handlers `onDrop`, `onFilesDrop` and `onHTMLDrop`.
*/
export default function useOnBlockDrop( targetRootClientId, targetBlockIndex ) {
const hasUploadPermissions = useSelect(
( select ) => select( blockEditorStore ).getSettings().mediaUpload,
[]
);
const {
canInsertBlockType,
getBlockIndex,
getClientIdsOfDescendants,
hasUploadPermissions,
} = useSelect( ( select ) => {
const {
canInsertBlockType: _canInsertBlockType,
getBlockIndex: _getBlockIndex,
getClientIdsOfDescendants: _getClientIdsOfDescendants,
getSettings,
} = select( blockEditorStore );

return {
canInsertBlockType: _canInsertBlockType,
Copy link
Member

@gziolo gziolo Apr 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To have the full picture, this could also be refactored as:

return {
    ...pick( select( blockEditorStore ), [ 'canInsertBlockType, getBlockIndex, getClientIdsOfDescendants ),
    hasUploadPermissions: getSettings().mediaUpload,
};

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's doesn't solve my main problem: it's awkward to return selector functions in a mapping.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it's a workaround. I think for withDispatch we were providing an 3rd argument so it could use selectors without passing it down from withSelect. Here it won't work so your exploration is really worth conisdering.

getBlockIndex: _getBlockIndex,
getClientIdsOfDescendants: _getClientIdsOfDescendants,
hasUploadPermissions: getSettings().mediaUpload,
};
}, [] );

} = useSelect( blockEditorStore );
const {
insertBlocks,
moveBlocksToPosition,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ function selector( select ) {
isMultiSelecting,
getMultiSelectedBlockClientIds,
hasMultiSelection,
getBlockParents,
getSelectedBlockClientId,
} = select( blockEditorStore );

Expand All @@ -51,7 +50,6 @@ function selector( select ) {
isMultiSelecting: isMultiSelecting(),
multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(),
hasMultiSelection: hasMultiSelection(),
getBlockParents,
selectedBlockClientId: getSelectedBlockClientId(),
};
}
Expand All @@ -74,9 +72,9 @@ export default function useMultiSelection( ref ) {
isMultiSelecting,
multiSelectedBlockClientIds,
hasMultiSelection,
getBlockParents,
selectedBlockClientId,
} = useSelect( selector, [] );
const { getBlockParents } = useSelect( blockEditorStore );
const {
startMultiSelect,
stopMultiSelect,
Expand Down
6 changes: 2 additions & 4 deletions packages/block-library/src/buttons/edit.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,10 @@ export default function ButtonsEdit( {
const [ maxWidth, setMaxWidth ] = useState( 0 );
const { marginLeft: spacing } = styles.spacing;

const { getBlockOrder, isInnerButtonSelected, shouldDelete } = useSelect(
const { isInnerButtonSelected, shouldDelete } = useSelect(
( select ) => {
const {
getBlockCount,
getBlockOrder: _getBlockOrder,
getBlockParents,
getSelectedBlockClientId,
} = select( blockEditorStore );
Expand All @@ -56,7 +55,6 @@ export default function ButtonsEdit( {
);

return {
getBlockOrder: _getBlockOrder,
isInnerButtonSelected: selectedBlockParents[ 0 ] === clientId,
// The purpose of `shouldDelete` check is giving the ability to
// pass to mobile toolbar function called `onDelete` which removes
Expand All @@ -67,7 +65,7 @@ export default function ButtonsEdit( {
},
[ clientId ]
);

const { getBlockOrder } = useSelect( blockEditorStore );
const { insertBlock, removeBlock, selectBlock } = useDispatch(
blockEditorStore
);
Expand Down
21 changes: 20 additions & 1 deletion packages/data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -793,9 +793,28 @@ any price in the state for that currency is retrieved. If the currency prop
doesn't change and other props are passed in that do change, the price will
not change because the dependency is just the currency.

When data is only used in an event callback, the data should not be retrieved
on render, so it may be useful to get the selectors function instead.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should say something like: don't use this API while calling the selectors directly on the render function, your component won't rerender when changes happen.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually makes me wonder: why should we retrieve the selectors during render at all if we could also retrieve them in the callback itself? In other words, in the callbacks, we could just use const { selector } = select( key )?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The global select/dispatch/subscribe function should be avoided as much as we can. These don't rely on the RegistryProvider. For me ideally, these should become WP-only APIs added as inline styles and not something the package exports by default.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would have the additional benefit of not having to pass the function as a dependency of callbacks. I'll explore this quickly in a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you misread my comment, we shouldn't do it :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, Github didn't show me your comment at the time so I didn't see it :)


**Don't use `useSelect` this way when calling the selectors in the render
function because your component won't re-render on a data change.**

```js
import { useSelect } from '@wordpress/data';

function Paste( { children } ) {
const { getSettings } = useSelect( 'my-shop' );
function onPaste() {
// Do something with the settings.
const settings = getSettings();
}
return <div onPaste={ onPaste }>{ children }</div>;
}
```

_Parameters_

- _\_mapSelect_ `Function`: Function called on every state change. The returned value is exposed to the component implementing this hook. The function receives the `registry.select` method on the first argument and the `registry` on the second argument.
- _\_mapSelect_ `Function|WPDataStore|string`: Function called on every state change. The returned value is exposed to the component implementing this hook. The function receives the `registry.select` method on the first argument and the `registry` on the second argument. When a store key is passed, all selectors for the store will be returned. This is only meant for usage of these selectors in event callbacks, not for data needed to create the element tree.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added documentation now. I feel like we're overloading the useSelect a bit though, so this may be another argument to create a separate useSelectForCallbacks hook. It could be simpler code-wise as well since we don't need to add logic for skipping the mapping.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way, it would be rather confusing whatever we do at this stage. We could have useSelectors or something like that but you still need to read docs to understand the difference so maybe it's fine to add jQuery-like flexibility in terms what passed arguments trigger 😄

- _deps_ `Array`: If provided, this memoizes the mapSelect so the same `mapSelect` is invoked on every state change unless the dependencies change.

_Returns_
Expand Down
Loading