diff --git a/server/dataLoader.js b/server/dataLoader.js index 7f17051c..6b609812 100644 --- a/server/dataLoader.js +++ b/server/dataLoader.js @@ -1,9 +1,9 @@ const url = require('url'); -const { matchPathname, configureStore } = require('./importer'); +const { matchPathname, configureStore, routeConfiguration } = require('./importer'); exports.loadData = function(requestUrl, sdk) { const { pathname, query } = url.parse(requestUrl); - const matchedRoutes = matchPathname(pathname); + const matchedRoutes = matchPathname(pathname, routeConfiguration); const store = configureStore(sdk); diff --git a/server/importer.js b/server/importer.js index a09e6de0..83eb744f 100644 --- a/server/importer.js +++ b/server/importer.js @@ -16,4 +16,5 @@ module.exports = { renderApp: mainJs.default, matchPathname: mainJs.matchPathname, configureStore: mainJs.configureStore, + routeConfiguration: mainJs.routeConfiguration, }; diff --git a/src/Routes.js b/src/Routes.js index a97b92a3..d7b0e0bd 100644 --- a/src/Routes.js +++ b/src/Routes.js @@ -13,9 +13,29 @@ class RouteComponentRenderer extends Component { constructor(props) { super(props); this.canShowComponent = this.canShowComponent.bind(this); + this.callLoadData = this.callLoadData.bind(this); } + componentDidMount() { - const { match, location, route, dispatch } = this.props; + // Calling loadData on initial rendering (on client side). + this.callLoadData(this.props); + } + + componentWillReceiveProps(nextProps) { + // Calling loadData after initial rendering (on client side). + // This makes it possible to use loadData as default client side data loading technique. + // However it is better to fetch data before location change to avoid "Loading data" state. + this.callLoadData(nextProps); + } + + canShowComponent() { + const { isAuthenticated, route } = this.props; + const { auth } = route; + return !auth || isAuthenticated; + } + + callLoadData(props) { + const { match, location, route, dispatch } = props; const { loadData, name } = route; const shouldLoadData = typeof loadData === 'function' && this.canShowComponent(); @@ -31,11 +51,7 @@ class RouteComponentRenderer extends Component { }); } } - canShowComponent() { - const { isAuthenticated, route } = this.props; - const { auth } = route; - return !auth || isAuthenticated; - } + render() { const { route, match, location, staticContext, flattenedRoutes } = this.props; const { component: RouteComponent } = route; @@ -65,6 +81,7 @@ RouteComponentRenderer.propTypes = { search: string.isRequired, }).isRequired, staticContext: object.isRequired, + // eslint-disable-next-line react/no-unused-prop-types dispatch: func.isRequired, }; diff --git a/src/containers/SearchPage/SearchPage.duck.js b/src/containers/SearchPage/SearchPage.duck.js index f1e4df77..bb76fffe 100644 --- a/src/containers/SearchPage/SearchPage.duck.js +++ b/src/containers/SearchPage/SearchPage.duck.js @@ -9,6 +9,7 @@ export const SEARCH_LISTINGS_ERROR = 'app/SearchPage/SEARCH_LISTINGS_ERROR'; // ================ Reducer ================ // const initialState = { + pagination: null, searchParams: null, searchInProgress: false, searchListingsError: null, @@ -29,7 +30,12 @@ const listingPageReducer = (state = initialState, action = {}) => { searchListingsError: null, }; case SEARCH_LISTINGS_SUCCESS: - return { ...state, searchInProgress: false, currentPageResultIds: resultIds(payload.data) }; + return { + ...state, + currentPageResultIds: resultIds(payload.data), + pagination: payload.data.meta, + searchInProgress: false, + }; case SEARCH_LISTINGS_ERROR: // eslint-disable-next-line no-console console.error(payload); @@ -63,11 +69,11 @@ export const searchListings = searchParams => (dispatch, getState, sdk) => { dispatch(searchListingsRequest(searchParams)); - const { origin, include = [] } = searchParams; + const { origin, include = [], page, per_page } = searchParams; const searchOrQuery = origin ? sdk.listings.search(searchParams) - : sdk.listings.query({ include }); + : sdk.listings.query({ include, page, per_page }); return searchOrQuery .then(response => { diff --git a/src/containers/SearchPage/SearchPage.js b/src/containers/SearchPage/SearchPage.js index 8ea0c9a5..6bed223a 100644 --- a/src/containers/SearchPage/SearchPage.js +++ b/src/containers/SearchPage/SearchPage.js @@ -2,23 +2,38 @@ import React, { PropTypes } from 'react'; import classNames from 'classnames'; import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; +import { withRouter } from 'react-router-dom'; import config from '../../config'; +import { createResourceLocatorString } from '../../util/routes'; import { parse } from '../../util/urlHelpers'; +import * as propTypes from '../../util/propTypes'; import { getListingsById } from '../../ducks/sdk.duck'; import { FilterPanel, - ListingCard, ListingCardSmall, MapPanel, PageLayout, SearchResultsPanel, } from '../../components'; import { searchListings } from './SearchPage.duck'; - import css from './SearchPage.css'; +// TODO Pagination page size might need to be dynamic on responsive page layouts +const RESULT_PAGE_SIZE = 12; + export const SearchPageComponent = props => { - const { tab, listings, searchParams, searchInProgress, searchListingsError } = props; + const { + flattenedRoutes, + listings, + location, + pagination, + push: historyPush, + searchInProgress, + searchListingsError, + searchParams, + tab, + } = props; + const totalItems = pagination ? pagination.total_items : 0; const filtersClassName = classNames(css.filters, { [css.open]: tab === 'filters' }); const listingsClassName = classNames(css.listings, { [css.open]: tab === 'listings' }); @@ -35,7 +50,7 @@ export const SearchPageComponent = props => { const resultsFound = (
-