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

Add useStoreSelectors hook #26692

Merged
merged 15 commits into from
Nov 6, 2020
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"@storybook/addon-viewport": "5.3.2",
"@storybook/react": "6.0.21",
"@testing-library/react": "10.0.2",
"@testing-library/react-hooks": "3.4.2",
"@types/classnames": "2.2.10",
"@types/eslint": "6.8.0",
"@types/estree": "0.0.44",
Expand Down
4 changes: 4 additions & 0 deletions packages/data/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New Feature

- Expose `useStoreSelectors` hook for usage in functional components. ([#26692](https://github.com/WordPress/gutenberg/pull/26692))

## 4.6.0 (2019-06-12)

### New Feature
Expand Down
42 changes: 42 additions & 0 deletions packages/data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,48 @@ _Returns_

- `Function`: A custom react hook.

<a name="useStoreSelectors" href="#useStoreSelectors">#</a> **useStoreSelectors**

Custom react hook for retrieving selectors from registered stores

_Usage_

```js
const { useStoreSelectors } = wp.data;

function HammerPriceDisplay( { currency } ) {
const price = useStoreSelectors(
'my-shop',
( { getPrice } ) => getPrice( 'hammer', currency ),
[ currency ]
);
return new Intl.NumberFormat( 'en-US', {
style: 'currency',
currency,
} ).format( price );
}

// Rendered in the application:
// <HammerPriceDisplay currency="USD" />
```

In the above example, when `HammerPriceDisplay` is rendered into an
application, the price will be retrieved from the store state using the
`mapSelectors` callback on `useStoreSelectors`. If the currency prop changes then
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.

_Parameters_

- _storeKey_ `string`: Store to return selectors from.
- _mapSelectors_ `Function`: Function called on every state change. The returned value is exposed to the component implementing this hook. The function receives the object with all the store selectors as its only argument.
- _deps_ `Array`: If provided, this memoizes the mapSelect so the same `mapSelect` is invoked on every state change unless the dependencies change.

_Returns_

- `Function`: A custom react hook.

<a name="withDispatch" href="#withDispatch">#</a> **withDispatch**

Higher-order component used to add dispatch props using registered action
Expand Down
50 changes: 50 additions & 0 deletions packages/data/src/components/use-store-selectors/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Internal dependencies
*/
import useSelect from '../use-select';

/**
* Custom react hook for retrieving selectors from registered stores
*
* @param {string} storeKey Store to return selectors from.
* @param {Function} mapSelectors Function called on every state change. The
* returned value is exposed to the component
* implementing this hook. The function receives
* the object with all the store selectors as
* its only argument.
* @param {Array} deps If provided, this memoizes the mapSelect so the
* same `mapSelect` is invoked on every state
* change unless the dependencies change.
*
* @example
* ```js
* const { useStoreSelectors } = wp.data;
*
* function HammerPriceDisplay( { currency } ) {
* const price = useStoreSelectors(
* 'my-shop',
* ( { getPrice } ) => getPrice( 'hammer', currency ),
* [ currency ]
* );
* return new Intl.NumberFormat( 'en-US', {
* style: 'currency',
* currency,
* } ).format( price );
* }
*
* // Rendered in the application:
* // <HammerPriceDisplay currency="USD" />
* ```
*
* In the above example, when `HammerPriceDisplay` is rendered into an
* application, the price will be retrieved from the store state using the
* `mapSelectors` callback on `useStoreSelectors`. If the currency prop changes then
* 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.
*
* @return {Function} A custom react hook.
*/
export default function useStoreSelectors( storeKey, mapSelectors, deps = [] ) {
return useSelect( ( select ) => mapSelectors( select( storeKey ) ), deps );
}
45 changes: 45 additions & 0 deletions packages/data/src/components/use-store-selectors/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* External dependencies
*/
import { renderHook } from '@testing-library/react-hooks';

/**
* WordPress dependencies
*/
import { RegistryProvider } from '@wordpress/data';

/**
* Internal dependencies
*/
import { createRegistry } from '../../../registry';
import useStoreSelectors from '../index';

describe( 'useStoreSelectors', () => {
let registry;
beforeEach( () => {
registry = createRegistry();
} );

it( 'wraps useSelect', () => {
registry.registerStore( 'testStore', {
reducer: () => ( { foo: 'bar' } ),
selectors: {
testSelector: ( state, key ) => state[ key ],
},
} );

const wrapper = ( { children } ) => (
<RegistryProvider value={ registry }>{ children }</RegistryProvider>
);

const useHook = () =>
useStoreSelectors( 'testStore', ( { testSelector } ) =>
testSelector( 'foo' )
);

const { result } = renderHook( useHook, { wrapper } );

// ensure expected state was rendered
expect( result.current ).toEqual( 'bar' );
} );
} );
1 change: 1 addition & 0 deletions packages/data/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export {
useRegistry,
} from './components/registry-provider';
export { default as useSelect } from './components/use-select';
export { default as useStoreSelectors } from './components/use-store-selectors';
export { useDispatch } from './components/use-dispatch';
export { AsyncModeProvider } from './components/async-mode-provider';
export { createRegistry } from './registry';
Expand Down