Skip to content

Commit

Permalink
Merge branch 'develop' into jimbo/minicart-height
Browse files Browse the repository at this point in the history
  • Loading branch information
dpatil-magento authored Jul 24, 2019
2 parents 71745da + 53852e3 commit 605a44c
Show file tree
Hide file tree
Showing 15 changed files with 347 additions and 264 deletions.
97 changes: 89 additions & 8 deletions packages/peregrine/lib/hooks/usePagination.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,95 @@
import { useMemo, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { getSearchParam } from './useSearchParam';

export const usePagination = () => {
const [currentPage, setCurrentPage] = useState(0);
const [totalPages, setTotalPages] = useState(null);
/**
* Sets a query parameter in history. Attempt to use React Router if provided
* otherwise fallback to builtins.
*/
const setQueryParam = ({ location, history, parameter, value }) => {
const { search } = location;
const queryParams = new URLSearchParams(search);
queryParams.set(parameter, value);

if (history.push) {
history.push({ search: queryParams.toString() });
} else {
// Use the native pushState. See https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method
history.pushState({ search: queryParams.toString() }, '');
}
};

const defaultInitialPage = 1;

/**
* `usePagination` provides a pagination state with `currentPage` and
* `totalPages` as well as an API for interacting with the state.
*
* @param {Object} location the location object, like window.location, or from react router
* @param {Object} history the history object, like window.history, or from react router
* @param {String} namespace the namespace to apply to the pagination query
* @param {String} parameter the name of the query parameter to use for page
* @param {Number} initialPage the initial current page value
* @param {Number} intialTotalPages the total pages expected to be usable by this hook
*
* TODO update with defaults
*
* @returns {[PaginationState, PaginationApi]}
*/
export const usePagination = ({
location = window.location,
history = window.history,
namespace = '',
parameter = 'page',
initialPage,
initialTotalPages = 1
} = {}) => {
const searchParam = namespace ? `${namespace}_${parameter}` : parameter;
if (!initialPage) {
// We need to synchronously fetch the initial page value from the query
// param otherwise we would initialize this value twice.
initialPage = parseInt(
getSearchParam(searchParam, location) || defaultInitialPage
);
}

const [currentPage, setCurrentPage] = useState(initialPage);
const [totalPages, setTotalPages] = useState(initialTotalPages);

const setPage = useCallback(
page => {
// Update the query parameter.
setQueryParam({
location,
history,
parameter: searchParam,
value: page
});

// Update the state object.
setCurrentPage(page);
},
[history, location, searchParam]
);

/**
* @typedef PaginationState
* @property {Number} currentPage the current page
* @property {Number} totalPages the total pages
*/
const paginationState = { currentPage, totalPages };
const api = useMemo(() => ({ setCurrentPage, setTotalPages }), [
setCurrentPage,
setTotalPages
]);

/**
* @typedef PaginationApi
* @property {Function} setCurrentPage
* @property {Function} setTotalPages
*/
const api = useMemo(
() => ({
setCurrentPage: setPage,
setTotalPages
}),
[setPage, setTotalPages]
);

return [paginationState, api];
};
9 changes: 8 additions & 1 deletion packages/peregrine/lib/hooks/useQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ export const useQuery = query => {
*/
const runQuery = useCallback(
async ({ variables }) => {
const payload = await apolloClient.query({ query, variables });
let payload;
try {
payload = await apolloClient.query({ query, variables });
} catch (e) {
payload = {
error: e
};
}
receiveResponse(payload);
},
[apolloClient, query, receiveResponse]
Expand Down
2 changes: 1 addition & 1 deletion packages/peregrine/lib/hooks/useSearchParam.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect } from 'react';

const getSearchParam = (parameter = '', location = window.location) => {
export const getSearchParam = (parameter = '', location = window.location) => {
const params = new URLSearchParams(location.search);

return params.get(parameter) || '';
Expand Down
29 changes: 23 additions & 6 deletions packages/peregrine/lib/hooks/useWindowSize.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const getSize = () => {
/**
* A hook that will return inner and outer height and width values whenever
* the window is resized.
*
* @kind function
* @private
*/
const useWindowSizeListener = () => {
const [windowSize, setWindowSize] = useState(getSize());
Expand All @@ -29,12 +32,18 @@ const useWindowSizeListener = () => {
};

/**
* This component contains a hook that listens for resize events. It is
* recommended to only create/use a single time at the top level of your app
* ex:
* <WindowSizeContextProvider>
* <App />
* </WindowSizeContextProvider>
* This component contains a hook that listens for resize events.
* Use this component with {@link useWindowSize} to get the value of the resized window.
*
* It is recommended to only create/use a single time at the top level of your app
* @summary A React context provider.
*
* @kind function
*
* @param {Object} props - React component props
*
* @return {Context.Provider} A [React context provider]{@link https://reactjs.org/docs/context.html}
*
*/
export const WindowSizeContextProvider = props => {
// This hook has side effects of adding listeners so we only want to create it
Expand All @@ -48,4 +57,12 @@ export const WindowSizeContextProvider = props => {
);
};

/**
* The current context value for the window size context.
* This value updates whenever the window is resized.
*
* Use this inside a {@link WindowSizeContextProvider}.
*
* @type number
*/
export const useWindowSize = () => useContext(WindowSizeContext);
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ exports[`renders the correct tree 1`] = `
<div
className="d"
>
<withRouter(Classify(Pagination))
<Classify(Pagination)
pageControl={Object {}}
/>
</div>
Expand Down
46 changes: 35 additions & 11 deletions packages/venia-concept/src/RootComponents/Category/category.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { usePagination, useQuery } from '@magento/peregrine';
import { toggleDrawer } from 'src/actions/app';
import catalogActions from 'src/actions/catalog';
import { mergeClasses } from 'src/classify';

import { fullPageLoadingIndicator } from 'src/components/LoadingIndicator';
import { connect } from 'src/drivers';
import { connect, withRouter } from 'src/drivers';
import { compose } from 'redux';
import categoryQuery from 'src/queries/getCategory.graphql';
import isObjectEmpty from 'src/util/isObjectEmpty';
import { getFilterParams } from 'src/util/getFilterParamsFromUrl';
Expand All @@ -15,22 +17,25 @@ import defaultClasses from './category.css';

const Category = props => {
const { filterClear, id, openDrawer, pageSize } = props;
const classes = mergeClasses(defaultClasses, props.classes);

const [paginationValues, paginationApi] = usePagination({
history: props.history,
location: props.location
});

const [paginationValues, paginationApi] = usePagination();
const { currentPage, totalPages } = paginationValues;
const { setCurrentPage, setTotalPages } = paginationApi;

const pageControl = {
currentPage,
setPage: setCurrentPage,
updateTotalPages: setTotalPages,
totalPages
};

const [queryResult, queryApi] = useQuery(categoryQuery);
const { data, error, loading } = queryResult;
const { runQuery, setLoading } = queryApi;
const classes = mergeClasses(defaultClasses, props.classes);

// clear any stale filters
useEffect(() => {
Expand Down Expand Up @@ -65,13 +70,27 @@ const Category = props => {

useEffect(() => {
setTotalPages(totalPagesFromData);
return () => {
setTotalPages(null);
};
}, [setTotalPages, totalPagesFromData]);

if (error) return <div>Data Fetch Error</div>;
// If we get an error after loading we should try to reset to page 1.
// If we continue to have errors after that, render an error message.
useEffect(() => {
if (error && !loading && currentPage !== 1) {
setCurrentPage(1);
}
}, [currentPage, error, loading, setCurrentPage]);

if (error && currentPage === 1 && !loading) {
return <div>Data Fetch Error</div>;
}

// show loading indicator until data has been fetched
// and pagination state has been updated
if (!totalPages) return fullPageLoadingIndicator;
// Show the loading indicator until data has been fetched.
if (!totalPagesFromData) {
return fullPageLoadingIndicator;
}

return (
<CategoryContent
Expand All @@ -96,6 +115,8 @@ Category.propTypes = {

Category.defaultProps = {
id: 3,
// TODO: This can be replaced by the value from `storeConfig when the PR,
// https://github.com/magento/graphql-ce/pull/650, is released.
pageSize: 6
};

Expand All @@ -104,7 +125,10 @@ const mapDispatchToProps = dispatch => ({
openDrawer: () => dispatch(toggleDrawer('filter'))
});

export default connect(
null,
mapDispatchToProps
export default compose(
withRouter,
connect(
null,
mapDispatchToProps
)
)(Category);
4 changes: 3 additions & 1 deletion packages/venia-concept/src/components/Gallery/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import React, { Component } from 'react';
import { arrayOf, number, shape } from 'prop-types';
import GalleryItem from './item';

const pageSize = 12;
// TODO: This can be replaced by the value from `storeConfig when the PR,
// https://github.com/magento/graphql-ce/pull/650, is released.
const pageSize = 6;
const emptyData = Array.from({ length: pageSize }).fill(null);

// inline the placeholder elements, since they're constant
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders nothing when there is only 1 page 1`] = `null`;

exports[`renders when there is more than 1 page 1`] = `
<div
className="root"
>
<button
onClick={[Function]}
>
<div />
1
</button>
<button
onClick={[Function]}
>
2
</button>
<button
onClick={[Function]}
>
3
</button>
</div>
`;
Loading

0 comments on commit 605a44c

Please sign in to comment.