mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-26 06:47:17 +10:00
Call component.loadData on server side (and client)
This commit is contained in:
parent
90bc748731
commit
71ab7c33e0
5 changed files with 118 additions and 38 deletions
|
|
@ -23,7 +23,8 @@
|
|||
"redux-saga": "^0.14.3",
|
||||
"sanitize.css": "^4.1.0",
|
||||
"sharetribe-scripts": "0.8.6",
|
||||
"source-map-support": "^0.4.11"
|
||||
"source-map-support": "^0.4.11",
|
||||
"url": "^0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^1.11.0",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const compression = require('compression');
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const qs = require('qs');
|
||||
const url = require('url');
|
||||
const _ = require('lodash');
|
||||
const React = require('react');
|
||||
const { createServerRenderContext } = require('react-router');
|
||||
|
|
@ -33,7 +34,9 @@ const buildPath = path.resolve(__dirname, '..', 'build');
|
|||
const manifestPath = path.join(buildPath, 'asset-manifest.json');
|
||||
const manifest = require(manifestPath);
|
||||
const mainJsPath = path.join(buildPath, manifest['main.js']);
|
||||
const renderApp = require(mainJsPath).default;
|
||||
const mainJs = require(mainJsPath);
|
||||
const renderApp = mainJs.default;
|
||||
const matchPathname = mainJs.matchPathname;
|
||||
|
||||
// The HTML build file is generated from the `public/index.html` file
|
||||
// and used as a template for server side rendering. The application
|
||||
|
|
@ -66,8 +69,21 @@ const template = _.template(indexHtml, {
|
|||
escape: reNoMatch,
|
||||
});
|
||||
|
||||
function render(url, context, preloadedState) {
|
||||
const { head, body } = renderApp(url, context, preloadedState);
|
||||
function render(requestUrl, context, preloadedState) {
|
||||
const { head, body } = renderApp(requestUrl, context, preloadedState);
|
||||
|
||||
const pathname = url.parse(requestUrl).pathname;
|
||||
const { matchedRoutes, params } = matchPathname(pathname);
|
||||
const component = matchedRoutes[0].component;
|
||||
|
||||
if (component.loadData) {
|
||||
component
|
||||
.loadData()
|
||||
.then((val) => {
|
||||
console.log('Data fetch resolved', val);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Preloaded state needs to be passed for client side too.
|
||||
// For security reasons we ensure that preloaded state is considered as a string
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { includes } from 'lodash';
|
||||
|
|
@ -39,41 +39,53 @@ const fakeListings = [
|
|||
},
|
||||
];
|
||||
|
||||
export const SearchPageComponent = props => {
|
||||
const { tab } = props;
|
||||
const selectedTab = includes(['filters','listings', 'map'], tab)
|
||||
? tab
|
||||
: 'listings';
|
||||
export class SearchPageComponent extends Component {
|
||||
|
||||
const filtersClassName = classNames(css.filters, {
|
||||
[css.open]: selectedTab === 'filters',
|
||||
});
|
||||
const listingsClassName = classNames(css.filters, {
|
||||
[css.open]: selectedTab === 'listings',
|
||||
});
|
||||
const mapClassName = classNames(css.filters, {
|
||||
[css.open]: selectedTab === 'map',
|
||||
});
|
||||
componentWillMount() {
|
||||
/* eslint-disable no-console */
|
||||
console.log('Client loads data');
|
||||
SearchPageComponent.loadData().then((val) => {
|
||||
console.log('Client fetched data', val);
|
||||
});
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout title="Search page">
|
||||
<div className={css.container}>
|
||||
<div className={filtersClassName}>
|
||||
<FilterPanel />
|
||||
render() {
|
||||
const { tab } = this.props;
|
||||
const selectedTab = includes(['filters','listings', 'map'], tab)
|
||||
? tab
|
||||
: 'listings';
|
||||
|
||||
const filtersClassName = classNames(css.filters, {
|
||||
[css.open]: selectedTab === 'filters',
|
||||
});
|
||||
const listingsClassName = classNames(css.filters, {
|
||||
[css.open]: selectedTab === 'listings',
|
||||
});
|
||||
const mapClassName = classNames(css.filters, {
|
||||
[css.open]: selectedTab === 'map',
|
||||
});
|
||||
|
||||
return (
|
||||
<PageLayout title="Search page">
|
||||
<div className={css.container}>
|
||||
<div className={filtersClassName}>
|
||||
<FilterPanel />
|
||||
</div>
|
||||
<div className={listingsClassName}>
|
||||
<SearchResultsPanel>
|
||||
{fakeListings.map(l => <ListingCard key={l.id} {...l} />)}
|
||||
</SearchResultsPanel>
|
||||
</div>
|
||||
<div className={mapClassName}>
|
||||
<MapPanel>
|
||||
{fakeListings.map(l => <ListingCardSmall key={l.id} {...l} />)}
|
||||
</MapPanel>
|
||||
</div>
|
||||
</div>
|
||||
<div className={listingsClassName}>
|
||||
<SearchResultsPanel>
|
||||
{fakeListings.map(l => <ListingCard key={l.id} {...l} />)}
|
||||
</SearchResultsPanel>
|
||||
</div>
|
||||
<div className={mapClassName}>
|
||||
<MapPanel>
|
||||
{fakeListings.map(l => <ListingCardSmall key={l.id} {...l} />)}
|
||||
</MapPanel>
|
||||
</div>
|
||||
</div>
|
||||
</PageLayout>
|
||||
);
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
SearchPageComponent.defaultProps = { tab: 'listings' };
|
||||
|
|
@ -82,6 +94,15 @@ const { string } = PropTypes;
|
|||
|
||||
SearchPageComponent.propTypes = { tab: string };
|
||||
|
||||
SearchPageComponent.loadData = () => (
|
||||
new Promise((resolve) => (
|
||||
// also node has a 'setTimeout' -> it's good for testing async call.
|
||||
setTimeout(() => {
|
||||
resolve(fakeListings);
|
||||
}, (Math.random() * 2000) + 1000)
|
||||
))
|
||||
);
|
||||
|
||||
/**
|
||||
* Container functions.
|
||||
* Since we add this to global store state with combineReducers, this will only get partial state
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import { ClientApp, renderApp } from './app';
|
||||
import configureStore from './store';
|
||||
import { matchLocation } from './routesConfiguration';
|
||||
|
||||
import './index.css';
|
||||
|
||||
|
|
@ -29,3 +30,5 @@ if (typeof window !== 'undefined') {
|
|||
|
||||
// Export the function for server side rendering.
|
||||
export default renderApp;
|
||||
|
||||
export { matchLocation };
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import React from 'react';
|
||||
import { find } from 'lodash';
|
||||
import { Redirect } from 'react-router';
|
||||
|
||||
// This will change to `matchPath` soonish
|
||||
import matchPattern from 'react-router/matchPattern'
|
||||
|
||||
import pathToRegexp from 'path-to-regexp';
|
||||
import {
|
||||
AuthenticationPage,
|
||||
|
|
@ -269,7 +273,42 @@ const toPathByRouteName = (nameToFind, routes) => {
|
|||
const pathByRouteName = (nameToFind, routes, params = {}) =>
|
||||
toPathByRouteName(nameToFind, routes)(params);
|
||||
|
||||
/**
|
||||
* matchRoutesToLocation helps to figure out which routes are related to given location.
|
||||
*/
|
||||
const matchRoutesToLocation = (routes, location, matchedRoutes=[], params={}) => {
|
||||
const parameters = { ...params };
|
||||
routes.forEach((route) => {
|
||||
const { exactly = false } = route;
|
||||
const match = !route.pattern ? true : matchPattern(route.pattern, location, exactly);
|
||||
|
||||
if (match) {
|
||||
matchedRoutes.push(route);
|
||||
|
||||
if (match.params) {
|
||||
Object.keys(match.params).forEach(key => { parameters[key] = match.params[key]; });
|
||||
}
|
||||
}
|
||||
|
||||
if (route.routes) {
|
||||
matchRoutesToLocation(route.routes, location, matchedRoutes, parameters);
|
||||
}
|
||||
});
|
||||
|
||||
return { matchedRoutes, params: parameters };
|
||||
}
|
||||
|
||||
const matchPathnameCreator = routes => (
|
||||
(location, matchedRoutes=[], params={}) =>
|
||||
matchRoutesToLocation(routes, { pathname: location }, matchedRoutes, params)
|
||||
);
|
||||
|
||||
/**
|
||||
* matchRoutesToLocation helps to figure out which routes are related to given location.
|
||||
*/
|
||||
const matchPathname = matchPathnameCreator(routesConfiguration);
|
||||
|
||||
// Exported helpers
|
||||
export { findRouteByName, flattenRoutes, toPathByRouteName, pathByRouteName };
|
||||
export { findRouteByName, flattenRoutes, matchPathname, toPathByRouteName, pathByRouteName };
|
||||
|
||||
export default routesConfiguration;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue