mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 20:53:24 +10:00
Merge pull request #71 from sharetribe/server-data-loading
Server side data loading: phase 1
This commit is contained in:
commit
a5cf87c15d
31 changed files with 148 additions and 164 deletions
|
|
@ -7,6 +7,7 @@
|
|||
"basic-auth": "^1.1.0",
|
||||
"classnames": "^2.2.5",
|
||||
"compression": "^1.6.2",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"express": "^4.15.2",
|
||||
"helmet": "^3.5.0",
|
||||
"lodash": "^4.17.4",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"clean": "rm -rf build/*",
|
||||
"dev": "sharetribe-scripts start",
|
||||
"build": "sharetribe-scripts build",
|
||||
"format": "prettier --write --print-width 100 --single-quote --trailing-comma all 'server/**/*.js' 'src/**/*.js'",
|
||||
"format": "prettier --write --print-width 100 --single-quote --trailing-comma es5 'server/**/*.js' 'src/**/*.js'",
|
||||
"test": "sharetribe-scripts test --env=jsdom",
|
||||
"test-ci": "sharetribe-scripts test --env=jsdom --runInBand",
|
||||
"eject": "sharetribe-scripts eject",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,37 @@
|
|||
const { matchPathname } = require('./importer');
|
||||
const url = require('url');
|
||||
const { types } = require('sharetribe-sdk');
|
||||
const { matchPathname, configureStore } = require('./importer');
|
||||
|
||||
exports.loadData = function(requestUrl) {
|
||||
return Promise.resolve({});
|
||||
// Currently the SDK serialisation doesn't work with mixed types from
|
||||
// client bundle and server imports. To fix this temporarily, we wrap
|
||||
// the id param here for the server side instead of doing the same in
|
||||
// the loadData handler.
|
||||
// TODO: remove this once the SDK serialisation works
|
||||
const fixParams = params => {
|
||||
if (params.id) {
|
||||
return Object.assign({}, params, { id: new types.UUID(params.id) });
|
||||
}
|
||||
return params;
|
||||
};
|
||||
|
||||
exports.loadData = function(requestUrl, sdk) {
|
||||
const { pathname, query } = url.parse(requestUrl, true);
|
||||
const matchedRoutes = matchPathname(pathname);
|
||||
|
||||
const store = configureStore(sdk);
|
||||
|
||||
const dataLoadingCalls = matchedRoutes.reduce(
|
||||
(calls, match) => {
|
||||
const { route, params } = match;
|
||||
if (typeof route.loadData === 'function' && !route.auth) {
|
||||
calls.push(store.dispatch(route.loadData(params, query)));
|
||||
}
|
||||
return calls;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return Promise.all(dataLoadingCalls).then(() => {
|
||||
return store.getState();
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
/* eslint-disable import/prefer-default-export, no-unused-vars */
|
||||
const fakeListings = [
|
||||
{
|
||||
id: 123,
|
||||
title: 'Banyan Studios',
|
||||
price: '55\u20AC / day',
|
||||
description: 'Organic Music Production in a Sustainable, Ethical and Professional Studio.',
|
||||
location: 'New York, NY \u2022 40mi away',
|
||||
review: { count: '8 reviews', rating: '4' },
|
||||
author: {
|
||||
name: 'The Stardust Collective',
|
||||
avatar: 'http://placehold.it/44x44',
|
||||
review: { rating: '4' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 1234,
|
||||
title: 'Pienix Studio',
|
||||
price: '80\u20AC / day',
|
||||
description: 'Pienix Studio specializes in music mixing and mastering production.',
|
||||
location: 'New York, NY \u2022 6mi away',
|
||||
review: { count: '7 reviews', rating: '4' },
|
||||
author: { name: 'Juhan', avatar: 'http://placehold.it/44x44', review: { rating: '4' } },
|
||||
},
|
||||
];
|
||||
/* eslint-enable import/prefer-default-export */
|
||||
|
||||
exports.fetchListings = () => {
|
||||
const randomTime = Math.random() * 2000;
|
||||
const fakeResponseTime = 1000 + randomTime;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(fakeListings), fakeResponseTime);
|
||||
});
|
||||
};
|
||||
|
|
@ -19,8 +19,10 @@ require('source-map-support').install();
|
|||
const express = require('express');
|
||||
const helmet = require('helmet');
|
||||
const compression = require('compression');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const path = require('path');
|
||||
const qs = require('qs');
|
||||
const sharetribeSdk = require('sharetribe-sdk');
|
||||
const auth = require('./auth');
|
||||
const renderer = require('./renderer');
|
||||
const dataLoader = require('./dataLoader');
|
||||
|
|
@ -29,6 +31,8 @@ const buildPath = path.resolve(__dirname, '..', 'build');
|
|||
const env = process.env.NODE_ENV;
|
||||
const dev = env !== 'production';
|
||||
const PORT = process.env.PORT || 4000;
|
||||
const CLIENT_ID = '08ec69f6-d37e-414d-83eb-324e94afddf0';
|
||||
const BASE_URL = 'http://localhost:8088';
|
||||
const app = express();
|
||||
|
||||
// The helmet middleware sets various HTTP headers to improve security.
|
||||
|
|
@ -44,19 +48,29 @@ if (!dev) {
|
|||
|
||||
app.use(compression());
|
||||
app.use('/static', express.static(path.join(buildPath, 'static')));
|
||||
app.use(cookieParser());
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
const context = {};
|
||||
const filters = qs.parse(req.query);
|
||||
|
||||
const sdk = sharetribeSdk.createInstance({
|
||||
clientId: CLIENT_ID,
|
||||
baseUrl: BASE_URL,
|
||||
tokenStore: sharetribeSdk.tokenStore.expressCookieStore({
|
||||
clientId: CLIENT_ID,
|
||||
req,
|
||||
res,
|
||||
}),
|
||||
});
|
||||
|
||||
dataLoader
|
||||
.loadData(req.url)
|
||||
.loadData(req.url, sdk)
|
||||
.then(preloadedState => {
|
||||
const html = renderer.render(req.url, context, preloadedState);
|
||||
|
||||
const debugData = {
|
||||
url: req.url,
|
||||
preloadedState,
|
||||
context,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { Component, PropTypes } from 'react';
|
|||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { Switch, Route, withRouter } from 'react-router-dom';
|
||||
import { types } from 'sharetribe-sdk';
|
||||
import { NotFoundPage } from './containers';
|
||||
import { NamedRedirect } from './components';
|
||||
import { withFlattenedRoutes } from './util/routes';
|
||||
|
|
@ -9,6 +10,18 @@ import * as propTypes from './util/propTypes';
|
|||
|
||||
const { bool, arrayOf, object, func, shape, string, any } = PropTypes;
|
||||
|
||||
// Currently the SDK serialisation doesn't work with mixed types from
|
||||
// client bundle and server imports. To fix this temporarily, we wrap
|
||||
// the id param here for the client side instead of doing the same in
|
||||
// the loadData handler.
|
||||
// TODO: remove this once the SDK serialisation works
|
||||
const fixParams = params => {
|
||||
if (params.id) {
|
||||
return { ...params, id: new types.UUID(params.id) };
|
||||
}
|
||||
return params;
|
||||
};
|
||||
|
||||
class RouteComponentRenderer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -20,7 +33,7 @@ class RouteComponentRenderer extends Component {
|
|||
const shouldLoadData = typeof loadData === 'function' && this.canShowComponent();
|
||||
|
||||
if (shouldLoadData) {
|
||||
dispatch(loadData(match.params, {}))
|
||||
dispatch(loadData(fixParams(match.params), {}))
|
||||
.then(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`loadData success for ${name} route`);
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ ServerApp.propTypes = { url: string.isRequired, context: any.isRequired, store:
|
|||
export const renderApp = (url, serverContext, preloadedState) => {
|
||||
const store = configureStore(preloadedState);
|
||||
const body = ReactDOMServer.renderToString(
|
||||
<ServerApp url={url} context={serverContext} store={store} />,
|
||||
<ServerApp url={url} context={serverContext} store={store} />
|
||||
);
|
||||
const head = Helmet.rewind();
|
||||
return { head, body };
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class AddImagesTest extends Component {
|
|||
};
|
||||
});
|
||||
},
|
||||
1000,
|
||||
1000
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ describe('BookingInfo', () => {
|
|||
bookingPeriod="Jan 2nd - Jan 4th"
|
||||
bookingDuration="3 days"
|
||||
total="165\u20AC"
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ describe('HeroSection', () => {
|
|||
const tree = renderDeep(
|
||||
<HeroSection params={{ displayName: 'most-awesome-shop' }}>
|
||||
test
|
||||
</HeroSection>,
|
||||
</HeroSection>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ LocationPredictionsList.propTypes = {
|
|||
id: string.isRequired,
|
||||
description: string.isRequired,
|
||||
place_id: string.isRequired,
|
||||
}),
|
||||
})
|
||||
).isRequired,
|
||||
highlightedIndex: number,
|
||||
onSelectItem: func.isRequired,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ describe('NamedLinkComponent', () => {
|
|||
<div>
|
||||
<NamedLinkComponent {...aProps}>link to a</NamedLinkComponent>
|
||||
<NamedLinkComponent {...bProps}>link to b</NamedLinkComponent>
|
||||
</div>,
|
||||
</div>
|
||||
);
|
||||
const aLink = tree.children[0];
|
||||
const bLink = tree.children[1];
|
||||
|
|
@ -46,7 +46,7 @@ describe('NamedLink', () => {
|
|||
const tree = renderDeep(
|
||||
<RoutesProvider flattenedRoutes={routesConf}>
|
||||
<NamedLink name="SomePage" params={{ id }}>to SomePage</NamedLink>
|
||||
</RoutesProvider>,
|
||||
</RoutesProvider>
|
||||
);
|
||||
expect(tree.type).toEqual('a');
|
||||
expect(tree.props.href).toEqual(`/somepage/${id}`);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ describe('RoutesProvider', () => {
|
|||
Child.contextTypes = { flattenedRoutes: React.PropTypes.array };
|
||||
|
||||
const tree = renderDeep(
|
||||
<RoutesProvider flattenedRoutes={routesConf}><Child /></RoutesProvider>,
|
||||
<RoutesProvider flattenedRoutes={routesConf}><Child /></RoutesProvider>
|
||||
);
|
||||
expect(tree.children).toContain('SomePage');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
});
|
||||
|
||||
const AuthenticationPage = connect(mapStateToProps, mapDispatchToProps)(
|
||||
AuthenticationPageComponent,
|
||||
AuthenticationPageComponent
|
||||
);
|
||||
|
||||
export default AuthenticationPage;
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ class EditListingForm extends Component {
|
|||
{ id: 'EditListingForm.maxLength' },
|
||||
{
|
||||
maxLength: TITLE_MAX_LENGTH,
|
||||
},
|
||||
}
|
||||
);
|
||||
const maxLength60 = maxLength(maxLengthStr, TITLE_MAX_LENGTH);
|
||||
const imageRequiredStr = intl.formatMessage({ id: 'EditListingForm.imageRequired' });
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ describe('EditListingForm', () => {
|
|||
onImageUpload={v => v}
|
||||
onSubmit={v => v}
|
||||
onUpdateImageOrder={v => v}
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ describe('EditListingPageComponent', () => {
|
|||
onUpdateImageOrder={v => v}
|
||||
page={{ imageOrder: [], images: {} }}
|
||||
type="new"
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -167,7 +167,11 @@ const mapStateToProps = state => ({
|
|||
const ListingPage = connect(mapStateToProps)(injectIntl(ListingPageComponent));
|
||||
|
||||
ListingPage.loadData = params => {
|
||||
return showListing(new UUID(params.id));
|
||||
// TODO: wrap with UUID when the universal serialisation works with
|
||||
// SDK and types from the client bundle and the server import don't
|
||||
// differ
|
||||
const id = params.id;
|
||||
return showListing(id);
|
||||
};
|
||||
|
||||
export default ListingPage;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ describe('ListingPage', () => {
|
|||
marketplaceData={marketplaceData}
|
||||
intl={fakeIntl}
|
||||
onLoadListing={l => l}
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
|
@ -57,7 +57,7 @@ describe('ListingPage', () => {
|
|||
expect(e).toEqual(error);
|
||||
expect(show.mock.calls).toEqual([[{ id, include: ['author', 'images'] }]]);
|
||||
expect(dispatch.mock.calls).toEqual([[showListingRequest(id)], [showListingError(e)]]);
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ const lookupMap = included =>
|
|||
|
||||
return memo;
|
||||
},
|
||||
{},
|
||||
{}
|
||||
);
|
||||
|
||||
// Format the data as ListingCard component expects it and
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const { LatLng } = types;
|
|||
describe('SearchPageComponent', () => {
|
||||
it('matches snapshot', () => {
|
||||
const tree = renderShallow(
|
||||
<SearchPageComponent onLoadListings={v => v} dispatch={() => null} />,
|
||||
<SearchPageComponent onLoadListings={v => v} dispatch={() => null} />
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ if (typeof window !== 'undefined') {
|
|||
if (config.dev) {
|
||||
const actions = bindActionCreators(
|
||||
{ showListings, queryListings, searchListings, showMarketplace, showUsers },
|
||||
store.dispatch,
|
||||
store.dispatch
|
||||
);
|
||||
window.app = { config, sdk, sdkTypes: types, actions, store, sample, routeConfiguration };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export const updatedEntities = (oldEntities, apiResponse) => {
|
|||
entities[type][id.uuid] = entity ? combinedResourceObjects(entity, curr) : curr;
|
||||
return entities;
|
||||
},
|
||||
oldEntities,
|
||||
oldEntities
|
||||
);
|
||||
/* eslint-enable no-param-reassign */
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ export const denormalisedEntities = (entities, type, ids) => {
|
|||
}
|
||||
return ent;
|
||||
},
|
||||
entityData,
|
||||
entityData
|
||||
);
|
||||
}
|
||||
return entityData;
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ describe('data utils', () => {
|
|||
const listing1 = { id: new UUID('listing1'), type: 'listing' };
|
||||
const listing2 = { id: new UUID('listing2'), type: 'listing' };
|
||||
expect(() => combinedResourceObjects(listing1, listing2)).toThrow(
|
||||
'Cannot merge resource objects with different ids or types',
|
||||
'Cannot merge resource objects with different ids or types'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ describe('data utils', () => {
|
|||
const listing1 = { id: new UUID('listing1'), type: 'listing' };
|
||||
const author1 = { id: new UUID('author1'), type: 'author' };
|
||||
expect(() => combinedResourceObjects(listing1, author1)).toThrow(
|
||||
'Cannot merge resource objects with different ids or types',
|
||||
'Cannot merge resource objects with different ids or types'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -200,7 +200,7 @@ describe('data utils', () => {
|
|||
it('throws when selecting a nonexistent type', () => {
|
||||
const entities = { listing: { listing1: createListing('listing1') } };
|
||||
expect(() => denormalisedEntities(entities, 'user', [new UUID('user1')])).toThrow(
|
||||
'No entities of type user',
|
||||
'No entities of type user'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -208,7 +208,7 @@ describe('data utils', () => {
|
|||
const entities = { listing: { listing1: createListing('listing1') } };
|
||||
const ids = [new UUID('listing2')];
|
||||
expect(() => denormalisedEntities(entities, 'listing', ids)).toThrow(
|
||||
'Entity listing with id listing2 not found',
|
||||
'Entity listing with id listing2 not found'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -218,7 +218,7 @@ describe('data utils', () => {
|
|||
const entities = { listing: { listing1 } };
|
||||
const ids = [listing1.id];
|
||||
expect(() => denormalisedEntities(entities, 'listing', ids)).toThrow(
|
||||
'No entities of type user',
|
||||
'No entities of type user'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
/* eslint-disable import/prefer-default-export, no-unused-vars */
|
||||
export const fakeListings = [
|
||||
{
|
||||
id: 123,
|
||||
title: 'Banyan Studios',
|
||||
price: '55\u20AC / day',
|
||||
description: 'Organic Music Production in a Sustainable, Ethical and Professional Studio.',
|
||||
location: 'New York, NY \u2022 40mi away',
|
||||
review: { count: '8 reviews', rating: '4' },
|
||||
author: {
|
||||
name: 'The Stardust Collective',
|
||||
avatar: 'http://placehold.it/44x44',
|
||||
review: { rating: '4' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 1234,
|
||||
title: 'Pienix Studio',
|
||||
price: '80\u20AC / day',
|
||||
description: 'Pienix Studio specializes in music mixing and mastering production.',
|
||||
location: 'New York, NY \u2022 6mi away',
|
||||
review: { count: '7 reviews', rating: '4' },
|
||||
author: { name: 'Juhan', avatar: 'http://placehold.it/44x44', review: { rating: '4' } },
|
||||
},
|
||||
];
|
||||
/* eslint-enable import/prefer-default-export */
|
||||
|
||||
const fakeSDK = {
|
||||
fetchListings: () => {
|
||||
const randomTime = Math.random() * 2000;
|
||||
const fakeResponseTime = 1000 + randomTime;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(fakeListings), fakeResponseTime);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default fakeSDK;
|
||||
|
|
@ -15,7 +15,7 @@ const placeBounds = place => {
|
|||
const sw = place.geometry.viewport.getSouthWest();
|
||||
return new SDKLatLngBounds(
|
||||
new SDKLatLng(ne.lat(), ne.lng()),
|
||||
new SDKLatLng(sw.lat(), sw.lng()),
|
||||
new SDKLatLng(sw.lat(), sw.lng())
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
|
@ -39,9 +39,7 @@ export const getPlaceDetails = placeId =>
|
|||
service.getDetails({ placeId }, (place, status) => {
|
||||
if (status !== serviceStatus.OK) {
|
||||
reject(
|
||||
new Error(
|
||||
`Could not get details for place id "${placeId}", error status was "${status}"`,
|
||||
),
|
||||
new Error(`Could not get details for place id "${placeId}", error status was "${status}"`)
|
||||
);
|
||||
} else {
|
||||
resolve({
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export const image = shape({
|
|||
height: number.isRequired,
|
||||
name: string.isRequired,
|
||||
url: string.isRequired,
|
||||
}),
|
||||
})
|
||||
).isRequired,
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,8 +5,21 @@ import pathToRegexp from 'path-to-regexp';
|
|||
import routesConfiguration from '../routesConfiguration';
|
||||
import * as propTypes from './propTypes';
|
||||
|
||||
export const flattenRoutes = routesArray =>
|
||||
routesArray.reduce((a, b) => a.concat(b.routes ? [b].concat(flattenRoutes(b.routes)) : b), []);
|
||||
// Flatten the routes config.
|
||||
// TODO: flatten the original config and remove this function
|
||||
export const flattenRoutes = routes =>
|
||||
routes.reduce(
|
||||
(flatRoutes, route) => {
|
||||
const r = { ...route };
|
||||
delete r.routes;
|
||||
flatRoutes.push(r);
|
||||
if (route.routes) {
|
||||
return flatRoutes.concat(flattenRoutes(route.routes));
|
||||
}
|
||||
return flatRoutes;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const findRouteByName = (nameToFind, flattenedRoutes) =>
|
||||
find(flattenedRoutes, route => route.name === nameToFind);
|
||||
|
|
@ -30,48 +43,33 @@ export const pathByRouteName = (nameToFind, flattenedRoutes, params = {}) =>
|
|||
toPathByRouteName(nameToFind, flattenedRoutes)(params);
|
||||
|
||||
/**
|
||||
* matchRoutesToLocation helps to figure out which routes are related to given location.
|
||||
* Find the matching routes and their params for the given pathname
|
||||
*
|
||||
* @param {String} pathname - Full URL path from root with possible
|
||||
* search params and hash included
|
||||
*
|
||||
* @return {Array<{ route, params }>} - All matches as { route, params } objects
|
||||
*/
|
||||
const matchRoutesToLocation = (routes, location, matchedRoutes = [], params = {}) => {
|
||||
let parameters = { ...params };
|
||||
let matched = [...matchedRoutes];
|
||||
export const matchPathname = pathname => {
|
||||
// TODO: remove flattening when routesConfiguration is flat
|
||||
const flattenedRoutes = flattenRoutes(routesConfiguration);
|
||||
|
||||
routes.forEach(route => {
|
||||
const { exact = false } = route;
|
||||
const match = !route.path || matchPath(location.pathname, route.path, { exact });
|
||||
|
||||
if (match) {
|
||||
matched.push(route);
|
||||
|
||||
if (match.params) {
|
||||
parameters = { ...parameters, ...match.params };
|
||||
return flattenedRoutes.reduce(
|
||||
(matches, route) => {
|
||||
const { exact = false } = route;
|
||||
const match = !route.path || matchPath(pathname, route.path, { exact });
|
||||
if (match) {
|
||||
matches.push({
|
||||
route,
|
||||
params: match.params || {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (route.routes) {
|
||||
const { matchedRoutes: subRouteMatches, params: subRouteParams } = matchRoutesToLocation(
|
||||
route.routes,
|
||||
location,
|
||||
matched,
|
||||
parameters,
|
||||
);
|
||||
matched = matched.concat(subRouteMatches);
|
||||
parameters = { ...parameters, ...subRouteParams };
|
||||
}
|
||||
});
|
||||
|
||||
return { matchedRoutes: matched, params: parameters };
|
||||
return matches;
|
||||
},
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
const matchPathnameCreator = routes =>
|
||||
(location, matchedRoutes = [], params = {}) =>
|
||||
matchRoutesToLocation(routes, { pathname: location }, matchedRoutes, params);
|
||||
|
||||
/**
|
||||
* matchRoutesToLocation helps to figure out which routes are related to given location.
|
||||
*/
|
||||
export const matchPathname = matchPathnameCreator(routesConfiguration);
|
||||
|
||||
/**
|
||||
* A higher order component (HOC) to take the flattened routes from
|
||||
* the context that the RoutesProvider component has provided.
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ describe('withFlattenedRoutes', () => {
|
|||
const shallowTree = renderShallow(
|
||||
<RoutesProvider flattenedRoutes={routes}>
|
||||
<Comp />
|
||||
</RoutesProvider>,
|
||||
</RoutesProvider>
|
||||
);
|
||||
expect(shallowTree).toMatchSnapshot();
|
||||
const deepTree = renderDeep(
|
||||
<RoutesProvider flattenedRoutes={routes}>
|
||||
<Comp />
|
||||
</RoutesProvider>,
|
||||
</RoutesProvider>
|
||||
);
|
||||
expect(deepTree).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ export const createRequestTypes = base =>
|
|||
acc[type] = `${base}.${type}`;
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
{}
|
||||
);
|
||||
/* eslint-enable import/prefer-default-export */
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const renderDeep = component => {
|
|||
const comp = renderer.create(
|
||||
<TestProvider>
|
||||
{component}
|
||||
</TestProvider>,
|
||||
</TestProvider>
|
||||
);
|
||||
return comp.toJSON();
|
||||
};
|
||||
|
|
|
|||
18
yarn.lock
18
yarn.lock
|
|
@ -1524,6 +1524,13 @@ convert-source-map@^1.1.0:
|
|||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.4.0.tgz#e3dad195bf61bfe13a7a3c73e9876ec14a0268f3"
|
||||
|
||||
cookie-parser@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.3.tgz#0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5"
|
||||
dependencies:
|
||||
cookie "0.3.1"
|
||||
cookie-signature "1.0.6"
|
||||
|
||||
cookie-signature@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
|
|
@ -2042,16 +2049,7 @@ error-ex@^1.2.0:
|
|||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
es-abstract@^1.6.1:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.6.1.tgz#bb8a2064120abcf928a086ea3d9043114285ec99"
|
||||
dependencies:
|
||||
es-to-primitive "^1.1.1"
|
||||
function-bind "^1.1.0"
|
||||
is-callable "^1.1.3"
|
||||
is-regex "^1.0.3"
|
||||
|
||||
es-abstract@^1.7.0:
|
||||
es-abstract@^1.6.1, es-abstract@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c"
|
||||
dependencies:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue