Add useCollection and useCollectionHeader hooks (closes: woocommerce/woocommerce-blocks#1096) (https://github.com/woocommerce/woocommerce-blocks/pull/1099)
* add use-collection hook and tests * Add use-collection-header hook * update use-store-products hook to implement useCollection and useCollection header under the hood
This commit is contained in:
parent
b029837889
commit
7e3f5e8ab9
|
@ -0,0 +1,236 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import TestRenderer, { act } from 'react-test-renderer';
|
||||||
|
import { createRegistry, RegistryProvider } from '@wordpress/data';
|
||||||
|
import { Component as ReactComponent } from '@wordpress/element';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { useCollection } from '../use-collection';
|
||||||
|
import { COLLECTIONS_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
||||||
|
|
||||||
|
jest.mock( '@woocommerce/block-data', () => ( {
|
||||||
|
__esModule: true,
|
||||||
|
COLLECTIONS_STORE_KEY: 'test/store',
|
||||||
|
} ) );
|
||||||
|
|
||||||
|
class TestErrorBoundary extends ReactComponent {
|
||||||
|
constructor( props ) {
|
||||||
|
super( props );
|
||||||
|
this.state = { hasError: false, error: {} };
|
||||||
|
}
|
||||||
|
static getDerivedStateFromError( error ) {
|
||||||
|
// Update state so the next render will show the fallback UI.
|
||||||
|
return { hasError: true, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if ( this.state.hasError ) {
|
||||||
|
return <div error={ this.state.error } />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe( 'useStoreProducts', () => {
|
||||||
|
let registry, mocks, renderer;
|
||||||
|
const getProps = ( testRenderer ) => {
|
||||||
|
const { results, isLoading } = testRenderer.root.findByType(
|
||||||
|
'div'
|
||||||
|
).props;
|
||||||
|
return {
|
||||||
|
results,
|
||||||
|
isLoading,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getWrappedComponents = ( Component, props ) => (
|
||||||
|
<RegistryProvider value={ registry }>
|
||||||
|
<TestErrorBoundary>
|
||||||
|
<Component { ...props } />
|
||||||
|
</TestErrorBoundary>
|
||||||
|
</RegistryProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const getTestComponent = () => ( { options } ) => {
|
||||||
|
const items = useCollection( options );
|
||||||
|
return <div { ...items } />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setUpMocks = () => {
|
||||||
|
mocks = {
|
||||||
|
selectors: {
|
||||||
|
getCollection: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation( () => ( { foo: 'bar' } ) ),
|
||||||
|
hasFinishedResolution: jest.fn().mockReturnValue( true ),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
registry.registerStore( storeKey, {
|
||||||
|
reducer: () => ( {} ),
|
||||||
|
selectors: mocks.selectors,
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach( () => {
|
||||||
|
registry = createRegistry();
|
||||||
|
mocks = {};
|
||||||
|
renderer = null;
|
||||||
|
setUpMocks();
|
||||||
|
} );
|
||||||
|
it(
|
||||||
|
'should throw an error if an options object is provided without ' +
|
||||||
|
'a namespace property',
|
||||||
|
() => {
|
||||||
|
const TestComponent = getTestComponent();
|
||||||
|
act( () => {
|
||||||
|
renderer = TestRenderer.create(
|
||||||
|
getWrappedComponents( TestComponent, {
|
||||||
|
options: {
|
||||||
|
resourceName: 'products',
|
||||||
|
query: { bar: 'foo' },
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
const props = renderer.root.findByType( 'div' ).props;
|
||||||
|
expect( props.error.message ).toMatch( /options object/ );
|
||||||
|
expect( console ).toHaveErrored( /your React components:/ );
|
||||||
|
renderer.unmount();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
it(
|
||||||
|
'should throw an error if an options object is provided without ' +
|
||||||
|
'a resourceName property',
|
||||||
|
() => {
|
||||||
|
const TestComponent = getTestComponent();
|
||||||
|
act( () => {
|
||||||
|
renderer = TestRenderer.create(
|
||||||
|
getWrappedComponents( TestComponent, {
|
||||||
|
options: {
|
||||||
|
namespace: 'test/store',
|
||||||
|
query: { bar: 'foo' },
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
const props = renderer.root.findByType( 'div' ).props;
|
||||||
|
expect( props.error.message ).toMatch( /options object/ );
|
||||||
|
expect( console ).toHaveErrored( /your React components:/ );
|
||||||
|
renderer.unmount();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
it(
|
||||||
|
'should return expected behaviour for equivalent query on props ' +
|
||||||
|
'across renders',
|
||||||
|
() => {
|
||||||
|
const TestComponent = getTestComponent();
|
||||||
|
act( () => {
|
||||||
|
renderer = TestRenderer.create(
|
||||||
|
getWrappedComponents( TestComponent, {
|
||||||
|
options: {
|
||||||
|
namespace: 'test/store',
|
||||||
|
resourceName: 'products',
|
||||||
|
query: { bar: 'foo' },
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
const { results } = getProps( renderer );
|
||||||
|
// rerender
|
||||||
|
act( () => {
|
||||||
|
renderer.update(
|
||||||
|
getWrappedComponents( TestComponent, {
|
||||||
|
options: {
|
||||||
|
namespace: 'test/store',
|
||||||
|
resourceName: 'products',
|
||||||
|
query: { bar: 'foo' },
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
// re-render should result in same products object because although
|
||||||
|
// query-state is a different instance, it's still equivalent.
|
||||||
|
const { results: newResults } = getProps( renderer );
|
||||||
|
expect( newResults ).toBe( results );
|
||||||
|
// now let's change the query passed through to verify new object
|
||||||
|
// is created.
|
||||||
|
// remember this won't actually change the results because the mock
|
||||||
|
// selector is returning an equivalent object when it is called,
|
||||||
|
// however it SHOULD be a new object instance.
|
||||||
|
act( () => {
|
||||||
|
renderer.update(
|
||||||
|
getWrappedComponents( TestComponent, {
|
||||||
|
options: {
|
||||||
|
namespace: 'test/store',
|
||||||
|
resourceName: 'products',
|
||||||
|
query: { foo: 'bar' },
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
const { results: resultsVerification } = getProps( renderer );
|
||||||
|
expect( resultsVerification ).not.toBe( results );
|
||||||
|
expect( resultsVerification ).toEqual( results );
|
||||||
|
renderer.unmount();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
it(
|
||||||
|
'should return expected behaviour for equivalent resourceValues on' +
|
||||||
|
' props across renders',
|
||||||
|
() => {
|
||||||
|
const TestComponent = getTestComponent();
|
||||||
|
act( () => {
|
||||||
|
renderer = TestRenderer.create(
|
||||||
|
getWrappedComponents( TestComponent, {
|
||||||
|
options: {
|
||||||
|
namespace: 'test/store',
|
||||||
|
resourceName: 'products',
|
||||||
|
resourceValues: [ 10, 20 ],
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
const { results } = getProps( renderer );
|
||||||
|
// rerender
|
||||||
|
act( () => {
|
||||||
|
renderer.update(
|
||||||
|
getWrappedComponents( TestComponent, {
|
||||||
|
options: {
|
||||||
|
namespace: 'test/store',
|
||||||
|
resourceName: 'products',
|
||||||
|
resourceValues: [ 10, 20 ],
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
// re-render should result in same products object because although
|
||||||
|
// query-state is a different instance, it's still equivalent.
|
||||||
|
const { results: newResults } = getProps( renderer );
|
||||||
|
expect( newResults ).toBe( results );
|
||||||
|
// now let's change the query passed through to verify new object
|
||||||
|
// is created.
|
||||||
|
// remember this won't actually change the results because the mock
|
||||||
|
// selector is returning an equivalent object when it is called,
|
||||||
|
// however it SHOULD be a new object instance.
|
||||||
|
act( () => {
|
||||||
|
renderer.update(
|
||||||
|
getWrappedComponents( TestComponent, {
|
||||||
|
options: {
|
||||||
|
namespace: 'test/store',
|
||||||
|
resourceName: 'products',
|
||||||
|
resourceValues: [ 20, 10 ],
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
const { results: resultsVerification } = getProps( renderer );
|
||||||
|
expect( resultsVerification ).not.toBe( results );
|
||||||
|
expect( resultsVerification ).toEqual( results );
|
||||||
|
renderer.unmount();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} );
|
|
@ -3,7 +3,6 @@
|
||||||
*/
|
*/
|
||||||
import TestRenderer, { act } from 'react-test-renderer';
|
import TestRenderer, { act } from 'react-test-renderer';
|
||||||
import { createRegistry, RegistryProvider } from '@wordpress/data';
|
import { createRegistry, RegistryProvider } from '@wordpress/data';
|
||||||
import { Component as ReactComponent } from '@wordpress/element';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -16,25 +15,6 @@ jest.mock( '@woocommerce/block-data', () => ( {
|
||||||
COLLECTIONS_STORE_KEY: 'test/store',
|
COLLECTIONS_STORE_KEY: 'test/store',
|
||||||
} ) );
|
} ) );
|
||||||
|
|
||||||
class TestErrorBoundary extends ReactComponent {
|
|
||||||
constructor( props ) {
|
|
||||||
super( props );
|
|
||||||
this.state = { hasError: false, error: {} };
|
|
||||||
}
|
|
||||||
static getDerivedStateFromError( error ) {
|
|
||||||
// Update state so the next render will show the fallback UI.
|
|
||||||
return { hasError: true, error };
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if ( this.state.hasError ) {
|
|
||||||
return <div error={ this.state.error } />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.props.children;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe( 'useStoreProducts', () => {
|
describe( 'useStoreProducts', () => {
|
||||||
let registry, mocks, renderer;
|
let registry, mocks, renderer;
|
||||||
const getProps = ( testRenderer ) => {
|
const getProps = ( testRenderer ) => {
|
||||||
|
@ -52,14 +32,12 @@ describe( 'useStoreProducts', () => {
|
||||||
|
|
||||||
const getWrappedComponents = ( Component, props ) => (
|
const getWrappedComponents = ( Component, props ) => (
|
||||||
<RegistryProvider value={ registry }>
|
<RegistryProvider value={ registry }>
|
||||||
<TestErrorBoundary>
|
<Component { ...props } />
|
||||||
<Component { ...props } />
|
|
||||||
</TestErrorBoundary>
|
|
||||||
</RegistryProvider>
|
</RegistryProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const getTestComponent = ( options ) => ( { query } ) => {
|
const getTestComponent = () => ( { query } ) => {
|
||||||
const items = useStoreProducts( query, options );
|
const items = useStoreProducts( query );
|
||||||
return <div { ...items } />;
|
return <div { ...items } />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -85,78 +63,6 @@ describe( 'useStoreProducts', () => {
|
||||||
renderer = null;
|
renderer = null;
|
||||||
setUpMocks();
|
setUpMocks();
|
||||||
} );
|
} );
|
||||||
it(
|
|
||||||
'should throw an error if an options object is provided without ' +
|
|
||||||
'a namespace property',
|
|
||||||
() => {
|
|
||||||
const TestComponent = getTestComponent( { modelName: 'products' } );
|
|
||||||
act( () => {
|
|
||||||
renderer = TestRenderer.create(
|
|
||||||
getWrappedComponents( TestComponent, {
|
|
||||||
query: { bar: 'foo' },
|
|
||||||
} )
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
const props = renderer.root.findByType( 'div' ).props;
|
|
||||||
expect( props.error.message ).toMatch( /options object/ );
|
|
||||||
expect( console ).toHaveErrored( /your React components:/ );
|
|
||||||
renderer.unmount();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
it(
|
|
||||||
'should throw an error if an options object is provided without ' +
|
|
||||||
'a modelName property',
|
|
||||||
() => {
|
|
||||||
const TestComponent = getTestComponent( {
|
|
||||||
namespace: '/wc/blocks',
|
|
||||||
} );
|
|
||||||
act( () => {
|
|
||||||
renderer = TestRenderer.create(
|
|
||||||
getWrappedComponents( TestComponent, {
|
|
||||||
query: { bar: 'foo' },
|
|
||||||
} )
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
const props = renderer.root.findByType( 'div' ).props;
|
|
||||||
expect( props.error.message ).toMatch( /options object/ );
|
|
||||||
expect( console ).toHaveErrored( /your React components:/ );
|
|
||||||
renderer.unmount();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
it( 'should use the default options if options not provided', () => {
|
|
||||||
const TestComponent = getTestComponent();
|
|
||||||
const {
|
|
||||||
getCollection,
|
|
||||||
getCollectionHeader,
|
|
||||||
hasFinishedResolution,
|
|
||||||
} = mocks.selectors;
|
|
||||||
act( () => {
|
|
||||||
renderer = TestRenderer.create(
|
|
||||||
getWrappedComponents( TestComponent, {
|
|
||||||
query: { bar: 'foo' },
|
|
||||||
} )
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
expect( getCollection ).toHaveBeenCalledWith(
|
|
||||||
{},
|
|
||||||
'/wc/blocks',
|
|
||||||
'products',
|
|
||||||
{ bar: 'foo' }
|
|
||||||
);
|
|
||||||
expect( getCollectionHeader ).toHaveBeenCalledWith(
|
|
||||||
{},
|
|
||||||
'x-wp-total',
|
|
||||||
'/wc/blocks',
|
|
||||||
'products',
|
|
||||||
{ bar: 'foo' }
|
|
||||||
);
|
|
||||||
expect( hasFinishedResolution ).toHaveBeenCalledWith(
|
|
||||||
{},
|
|
||||||
'getCollection',
|
|
||||||
[ '/wc/blocks', 'products', { bar: 'foo' } ]
|
|
||||||
);
|
|
||||||
renderer.unmount();
|
|
||||||
} );
|
|
||||||
it(
|
it(
|
||||||
'should return expected behaviour for equivalent query on props ' +
|
'should return expected behaviour for equivalent query on props ' +
|
||||||
'across renders',
|
'across renders',
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { COLLECTIONS_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
||||||
|
import { useSelect } from '@wordpress/data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { useShallowEqual } from './use-shallow-equal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a custom hook that is wired up to the `wc/store/collections` data
|
||||||
|
* store. Given a header key and a collections option object, this will ensure a
|
||||||
|
* component is kept up to date with the collection header value matching that
|
||||||
|
* query in the store state.
|
||||||
|
*
|
||||||
|
* @param {string} headerKey Used to indicate which header value to
|
||||||
|
* return for the given collection query.
|
||||||
|
* Example: `'x-wp-total'`
|
||||||
|
* @param {Object} options An object declaring the various
|
||||||
|
* collection arguments.
|
||||||
|
* @param {string} options.namespace The namespace for the collection.
|
||||||
|
* Example: `'/wc/blocks'`
|
||||||
|
* @param {string} options.resourceName The name of the resource for the
|
||||||
|
* collection. Example:
|
||||||
|
* `'products/attributes'`
|
||||||
|
* @param {array} options.resourceValues An array of values (in correct order)
|
||||||
|
* that are substituted in the route
|
||||||
|
* placeholders for the collection route.
|
||||||
|
* Example: `[10, 20]`
|
||||||
|
* @param {Object} options.query An object of key value pairs for the
|
||||||
|
* query to execute on the collection
|
||||||
|
* (optional). Example:
|
||||||
|
* `{ order: 'ASC', order_by: 'price' }`
|
||||||
|
*
|
||||||
|
* @return {Object} This hook will return an object with two properties:
|
||||||
|
* - value Whatever value is attached to the specified
|
||||||
|
* header.
|
||||||
|
* - isLoading A boolean indicating whether the header is
|
||||||
|
* loading (true) or not.
|
||||||
|
*/
|
||||||
|
export const useCollectionHeader = ( headerKey, options ) => {
|
||||||
|
const { namespace, resourceName, resourceValues, query } = options;
|
||||||
|
if ( ! namespace || ! resourceName ) {
|
||||||
|
throw new Error(
|
||||||
|
'The options object must have valid values for the namespace and ' +
|
||||||
|
'the modelName properties.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// ensure we feed the previous reference if it's equivalent
|
||||||
|
const currentQuery = useShallowEqual( query );
|
||||||
|
const currentResourceValues = useShallowEqual( resourceValues );
|
||||||
|
const { value, isLoading = true } = useSelect(
|
||||||
|
( select ) => {
|
||||||
|
const store = select( storeKey );
|
||||||
|
// filter out query if it is undefined.
|
||||||
|
const args = [
|
||||||
|
headerKey,
|
||||||
|
namespace,
|
||||||
|
resourceName,
|
||||||
|
currentQuery,
|
||||||
|
currentResourceValues,
|
||||||
|
].filter( ( item ) => typeof item !== 'undefined' );
|
||||||
|
return {
|
||||||
|
value: store.getCollectionHeader( ...args ),
|
||||||
|
isLoading: store.hasFinishedResolution(
|
||||||
|
'getCollectionHeader',
|
||||||
|
args
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[
|
||||||
|
headerKey,
|
||||||
|
namespace,
|
||||||
|
resourceName,
|
||||||
|
currentResourceValues,
|
||||||
|
currentQuery,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
isLoading,
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { COLLECTIONS_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
||||||
|
import { useSelect } from '@wordpress/data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { useShallowEqual } from './use-shallow-equal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a custom hook that is wired up to the `wc/store/collections` data
|
||||||
|
* store. Given a collections option object, this will ensure a component is
|
||||||
|
* kept up to date with the collection matching that query in the store state.
|
||||||
|
*
|
||||||
|
* @param {Object} options An object declaring the various
|
||||||
|
* collection arguments.
|
||||||
|
* @param {string} options.namespace The namespace for the collection.
|
||||||
|
* Example: `'/wc/blocks'`
|
||||||
|
* @param {string} options.resourceName The name of the resource for the
|
||||||
|
* collection. Example:
|
||||||
|
* `'products/attributes'`
|
||||||
|
* @param {array} options.resourceValues An array of values (in correct order)
|
||||||
|
* that are substituted in the route
|
||||||
|
* placeholders for the collection route.
|
||||||
|
* Example: `[10, 20]`
|
||||||
|
* @param {Object} options.query An object of key value pairs for the
|
||||||
|
* query to execute on the collection
|
||||||
|
* (optional). Example:
|
||||||
|
* `{ order: 'ASC', order_by: 'price' }`
|
||||||
|
*
|
||||||
|
* @return {Object} This hook will return an object with two properties:
|
||||||
|
* - results An array of collection items returned.
|
||||||
|
* - isLoading A boolean indicating whether the collection is
|
||||||
|
* loading (true) or not.
|
||||||
|
*/
|
||||||
|
export const useCollection = ( options ) => {
|
||||||
|
const { namespace, resourceName, resourceValues, query } = options;
|
||||||
|
if ( ! namespace || ! resourceName ) {
|
||||||
|
throw new Error(
|
||||||
|
'The options object must have valid values for the namespace and ' +
|
||||||
|
'the modelName properties.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// ensure we feed the previous reference if it's equivalent
|
||||||
|
const currentQuery = useShallowEqual( query );
|
||||||
|
const currentResourceValues = useShallowEqual( resourceValues );
|
||||||
|
const { results = [], isLoading = true } = useSelect(
|
||||||
|
( select ) => {
|
||||||
|
const store = select( storeKey );
|
||||||
|
// filter out query if it is undefined.
|
||||||
|
const args = [
|
||||||
|
namespace,
|
||||||
|
resourceName,
|
||||||
|
currentQuery,
|
||||||
|
currentResourceValues,
|
||||||
|
].filter( ( item ) => typeof item !== 'undefined' );
|
||||||
|
return {
|
||||||
|
results: store.getCollection( ...args ),
|
||||||
|
isLoading: store.hasFinishedResolution( 'getCollection', args ),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[ namespace, resourceName, currentResourceValues, currentQuery ]
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
results,
|
||||||
|
isLoading,
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,71 +1,40 @@
|
||||||
/**
|
|
||||||
* External dependencies
|
|
||||||
*/
|
|
||||||
import { COLLECTIONS_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
|
||||||
import { useSelect } from '@wordpress/data';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { useShallowEqual } from './use-shallow-equal';
|
import { useCollection } from './use-collection';
|
||||||
|
import { useCollectionHeader } from './use-collection-header';
|
||||||
const DEFAULT_OPTIONS = {
|
|
||||||
namespace: '/wc/blocks',
|
|
||||||
modelName: 'products',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a custom hook that is wired up to the `wc/store/collections` data
|
* This is a custom hook that is wired up to the `wc/store/collections` data
|
||||||
* store. Given a query object, this will ensure a component is kept up to date
|
* store for the `'wc/store/products'` route. Given a query object, this
|
||||||
* with the products matching that query in the store state.
|
* will ensure a component is kept up to date with the products matching that
|
||||||
|
* query in the store state.
|
||||||
*
|
*
|
||||||
* @param {Object} query An object containing any query arguments to be
|
* @param {Object} query An object containing any query arguments to be
|
||||||
* included with the collection request for the
|
* included with the collection request for the
|
||||||
* products. Does not have to be included.
|
* products. Does not have to be included.
|
||||||
* @param {Object} options An optional object for adjusting the namespace and
|
|
||||||
* modelName for the products query.
|
|
||||||
*
|
*
|
||||||
* @return {Object} This hook will return an object with three properties:
|
* @return {Object} This hook will return an object with three properties:
|
||||||
* - products An array of product objects.
|
* - products An array of product objects.
|
||||||
* - totalProducts The total number of products that match the
|
* - totalProducts The total number of products that match
|
||||||
* given query parameters.
|
* the given query parameters.
|
||||||
* - productsLoading A boolean indicating whether the products
|
* - productsLoading A boolean indicating whether the products
|
||||||
* are still loading or not.
|
* are still loading or not.
|
||||||
*/
|
*/
|
||||||
export const useStoreProducts = ( query, options = DEFAULT_OPTIONS ) => {
|
export const useStoreProducts = ( query ) => {
|
||||||
const { namespace, modelName } = options;
|
// @todo see @https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/1097
|
||||||
if ( ! namespace || ! modelName ) {
|
// where the namespace is going to be changed. Not doing in this pull.
|
||||||
throw new Error(
|
const collectionOptions = {
|
||||||
'If you provide an options object, you must have valid values ' +
|
namespace: '/wc/blocks',
|
||||||
'for the namespace and the modelName properties.'
|
resourceName: 'products',
|
||||||
);
|
query,
|
||||||
}
|
};
|
||||||
// ensure we feed the previous reference object if it's equivalent
|
const { results: products, isLoading: productsLoading } = useCollection(
|
||||||
const currentQuery = useShallowEqual( query );
|
collectionOptions
|
||||||
const {
|
);
|
||||||
products = [],
|
const { value: totalProducts } = useCollectionHeader(
|
||||||
totalProducts = 0,
|
'x-wp-total',
|
||||||
productsLoading = true,
|
collectionOptions
|
||||||
} = useSelect(
|
|
||||||
( select ) => {
|
|
||||||
const store = select( storeKey );
|
|
||||||
// filter out query if it is undefined.
|
|
||||||
const args = [ namespace, modelName, currentQuery ].filter(
|
|
||||||
( item ) => typeof item !== undefined
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
products: store.getCollection( ...args ),
|
|
||||||
totalProducts: store.getCollectionHeader(
|
|
||||||
'x-wp-total',
|
|
||||||
...args
|
|
||||||
),
|
|
||||||
productsLoading: store.hasFinishedResolution(
|
|
||||||
'getCollection',
|
|
||||||
args
|
|
||||||
),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[ namespace, modelName, currentQuery ]
|
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
products,
|
products,
|
||||||
|
|
Loading…
Reference in New Issue