diff --git a/README.md b/README.md index fbfceb3..4fe9e2a 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Runs linter. Only views can be SSR. Views are components that directly connected to a URL. To do SSR you need just 2 simple steps: -* Add `serverSideInitial` static method to your view to do side-effects, dispatch actions to store or return values and promises. +* Just add `serverSideInitial` static method to your view to do side-effects, dispatch actions to store or return values and promises. ``` static serverSideInitial({ dispatch, req }) { @@ -85,7 +85,7 @@ Only views can be SSR. Views are components that directly connected to a URL. To ``` -* Wrap the view with `withSSRData` HOC to get value returned by `serverSideInitial` as `props.initialSSRData`. +* Now get value returned by `serverSideInitial` as `props.initialSSRData`. ``` Component.propTypes = { @@ -93,7 +93,7 @@ Component.propTypes = { posts: PropTypes.array(), }; -export default withSSRData(Component); +export default Component; ``` ## Directory Layout diff --git a/src/AppRouter.jsx b/src/AppRouter.jsx index b1675fb..31c4b15 100644 --- a/src/AppRouter.jsx +++ b/src/AppRouter.jsx @@ -1,7 +1,7 @@ -import { renderRoutes } from 'react-router-config'; - import routes from 'src/configs/routes'; +import { renderRoutes } from './server/utils'; + function AppRouter() { return renderRoutes(routes); } diff --git a/src/configs/routes.js b/src/configs/routes.js index cc02a98..f1fcb47 100644 --- a/src/configs/routes.js +++ b/src/configs/routes.js @@ -2,6 +2,7 @@ import Home from 'src/views/Home'; const routes = [ { + path: '/', component: Home, }, ]; diff --git a/src/server/handlers/ssr-handler.js b/src/server/handlers/ssr-handler.js index 2146343..34a5498 100644 --- a/src/server/handlers/ssr-handler.js +++ b/src/server/handlers/ssr-handler.js @@ -13,7 +13,7 @@ import { initSSRContextValue } from 'src/services/initial-ssr-data/utils'; import { getEnv, isProd } from 'src/utils/env'; import AppRouter from 'src/AppRouter'; -import { findFinalComponent } from '../utils'; +import { findFinalComponent, getComponent } from '../utils'; import pageTemplate from '../templates/page-template.hbs'; // eslint-disable-next-line import/no-dynamic-require @@ -45,22 +45,37 @@ function renderPage({ appHtml, preloadedData, preloadedState }) { function getInitialPropsList({ renderBranch, store: { getState, dispatch }, req }) { return renderBranch.reduce( - (initialPropsList, { route: { component, path } }) => { + ( + initialPropsList, + { + route: + { + component: routeComponent, + render: routeRenderer, path, + }, + }, + ) => { + const component = getComponent(routeComponent, routeRenderer); + // Due to usage of HOCs, we have to traverse the component tree // to find the final wrapped component which contains the static server-side methods const { component: { serverSideInitial }, - hasSSRData: hasPreloadedData, + hasSSRData: wrappedInWithSSRData, } = findFinalComponent(component); if (!serverSideInitial) { return initialPropsList; } + const dataPromise = serverSideInitial({ getState, dispatch, req }); + const serverSideInitialReturns = dataPromise !== undefined; + const hasPreloadedData = serverSideInitialReturns || wrappedInWithSSRData; + return initialPropsList.concat({ path, hasPreloadedData, - dataPromise: serverSideInitial({ getState, dispatch, req }), + dataPromise, }); }, [], diff --git a/src/server/utils.js b/src/server/utils.js index 672fc4e..6d1b35f 100644 --- a/src/server/utils.js +++ b/src/server/utils.js @@ -1,4 +1,8 @@ +import React from 'react'; +import { Switch, Route } from 'react-router-dom'; + import { getEnv } from 'src/utils/env'; +import withSSRData from 'src/services/initial-ssr-data/withSSRData'; // Due to usage of HOCs, we have to traverse the component tree to find the // final wrapped component which contains the static server-side methods @@ -20,3 +24,44 @@ export function findFinalComponent(component) { hasSSRData: isWrappedInWithSSRDataHOC, }; } + +export function getComponent(component, routeRenderer) { + // because route renderers are a wrapper around the actual component, + // they return the element created by react and not the actual component so we + // need to point to the actual component which is stored in the 'type' property + // in the returned react element + const reactElement = routeRenderer ? routeRenderer() : null; + + return reactElement ? reactElement.type : component; +} + +export function renderRoutes(routes, extraProps = {}, switchProps = {}) { + if (!routes) return null; + + return ( + + {routes.map((route, index) => { + const { component: routeComponent, render: routeRenderer, ...rest } = route; + const component = getComponent(routeComponent, routeRenderer); + const { hasSSRData: wrappedInWithSSRData } = findFinalComponent(component); + const hasServerSideInitial = component.serverSideInitial; + const needsWithSSRData = hasServerSideInitial && !wrappedInWithSSRData; + + /* eslint react/no-array-index-key: 0 */ + return ( + { + const componentProps = { ...props, ...extraProps, route }; + + return (needsWithSSRData) + ? React.createElement(withSSRData(component), componentProps) + : React.createElement(component, componentProps); + }} + /> + ); + })} + + ); +} diff --git a/src/shared-components/withSSRData.jsx b/src/services/initial-ssr-data/withSSRData.jsx similarity index 95% rename from src/shared-components/withSSRData.jsx rename to src/services/initial-ssr-data/withSSRData.jsx index fd27a24..452ca07 100644 --- a/src/shared-components/withSSRData.jsx +++ b/src/services/initial-ssr-data/withSSRData.jsx @@ -2,10 +2,11 @@ import React, { Component } from 'react'; import { withRouter } from 'react-router'; import PropTypes from 'prop-types'; -import { getContext } from 'src/services/initial-ssr-data'; import { getDisplayName } from 'src/utils/hoc'; import { getEnv } from 'src/utils/env'; +import { getContext } from './index'; + function withSSRData(WrappedComponent) { class WithSSRDataComponent extends Component { componentWillUnmount() {