diff --git a/samples/react/server/server.js b/samples/react/server/server.js
index 4d4d4e6918..6c0779315b 100644
--- a/samples/react/server/server.js
+++ b/samples/react/server/server.js
@@ -7,7 +7,6 @@ import GraphQLClientFactory from '../src/lib/GraphQLClientFactory';
import config from '../src/temp/config';
import i18ninit from '../src/i18n';
import AppRoot, { routePatterns } from '../src/AppRoot';
-import { setServerSideRenderingState } from '../src/RouteHandler';
import { getHtmlTemplate } from './htmlTemplateFactory';
/** Asserts that a string replace actually replaced something */
@@ -45,7 +44,6 @@ export const appName = config.jssAppName;
export function renderView(callback, path, data, viewBag) {
try {
const state = parseServerData(data, viewBag);
- setServerSideRenderingState(state);
/*
GraphQL Data
@@ -64,7 +62,12 @@ export function renderView(callback, path, data, viewBag) {
// is included in the SSR'ed markup instead of whatever the 'loading' state is.
// Not using GraphQL? Use ReactDOMServer.renderToString() instead.
renderToStringWithData(
-
+
)
)
.then((renderedAppHtml) =>
diff --git a/samples/react/src/AppRoot.js b/samples/react/src/AppRoot.js
index 90f14a3839..a9e0e614db 100644
--- a/samples/react/src/AppRoot.js
+++ b/samples/react/src/AppRoot.js
@@ -1,9 +1,8 @@
import React from 'react';
-import { SitecoreContext } from '@sitecore-jss/sitecore-jss-react';
+import { SitecoreContext, SitecoreContextFactory } from '@sitecore-jss/sitecore-jss-react';
import { Route, Switch } from 'react-router-dom';
import { ApolloProvider } from 'react-apollo';
import componentFactory from './temp/componentFactory';
-import SitecoreContextFactory from './lib/SitecoreContextFactory';
import RouteHandler from './RouteHandler';
// This is the main JSX entry point of the app invoked by the renderer (server or client rendering).
@@ -22,21 +21,58 @@ export const routePatterns = [
// Not needed if not using connected GraphQL.
// SitecoreContext: provides component resolution and context services via withSitecoreContext
// Router: provides a basic routing setup that will resolve Sitecore item routes and allow for language URL prefixes.
-const AppRoot = ({ path, Router, graphQLClient }) => {
- const routeRenderFunction = (props) => ;
- return (
-
-
-
-
- {routePatterns.map((routePattern) => (
-
- ))}
-
-
-
-
- );
-};
+class AppRoot extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ ssrRenderComplete: false,
+ contextFactory: new SitecoreContextFactory()
+ }
+
+ if (props.ssrState && props.ssrState.sitecore && props.ssrState.sitecore.route) {
+ // set the initial sitecore context data if we got SSR initial state
+ this.state.contextFactory.setSitecoreContext({
+ route: props.ssrState.sitecore.route,
+ itemId: props.ssrState.sitecore.route.itemId,
+ ...props.ssrState.sitecore.context,
+ });
+ } else if (props.ssrState) {
+ this.state.contextFactory.setSitecoreContext(props.ssrState.sitecore.context)
+ } else {
+ this.state.contextFactory.setSitecoreContext(null);
+ }
+ }
+
+ setSsrRenderComplete = ssrRenderComplete => (
+ this.setState({
+ ssrRenderComplete
+ })
+ )
+
+ render() {
+ const { path, Router, graphQLClient } = this.props;
+
+ const routeRenderFunction = (props) =>
+ ;
+ return (
+
+
+
+
+ {routePatterns.map((routePattern) => (
+
+ ))}
+
+
+
+
+ );
+ }
+}
export default AppRoot;
diff --git a/samples/react/src/RouteHandler.js b/samples/react/src/RouteHandler.js
index 9e5220affa..b507d0adf5 100644
--- a/samples/react/src/RouteHandler.js
+++ b/samples/react/src/RouteHandler.js
@@ -1,8 +1,7 @@
import React from 'react';
import i18n from 'i18next';
import Helmet from 'react-helmet';
-import { isExperienceEditorActive, dataApi } from '@sitecore-jss/sitecore-jss-react';
-import SitecoreContextFactory from './lib/SitecoreContextFactory';
+import { isExperienceEditorActive, dataApi, withSitecoreContext } from '@sitecore-jss/sitecore-jss-react';
import { dataFetcher } from './dataFetcher';
import config from './temp/config';
import Layout from './Layout';
@@ -14,54 +13,33 @@ import NotFound from './NotFound';
// So react-router delegates all route rendering to this handler, which attempts to get the right
// route data from Sitecore - and if none exists, renders the not found component.
-let ssrInitialState = null;
-
-export default class RouteHandler extends React.Component {
+class RouteHandler extends React.Component {
constructor(props) {
super(props);
this.state = {
notFound: true,
- routeData: ssrInitialState, // null when client-side rendering
defaultLanguage: config.defaultLanguage,
};
- if (ssrInitialState && ssrInitialState.sitecore && ssrInitialState.sitecore.route) {
- // set the initial sitecore context data if we got SSR initial state
- SitecoreContextFactory.setSitecoreContext({
- route: ssrInitialState.sitecore.route,
- itemId: ssrInitialState.sitecore.route.itemId,
- ...ssrInitialState.sitecore.context,
- });
- }
+ const routeData = this.extractRouteData();
// route data from react-router - if route was resolved, it's not a 404
- if (props.route !== null) {
+ if (routeData !== null) {
this.state.notFound = false;
}
// if we have an initial SSR state, and that state doesn't have a valid route data,
// then this is a 404 route.
- if (ssrInitialState && (!ssrInitialState.sitecore || !ssrInitialState.sitecore.route)) {
+ if (routeData && (!routeData.sitecore || !routeData.sitecore.route)) {
this.state.notFound = true;
}
// if we have an SSR state, and that state has language data, set the current language
// (this makes the language of content follow the Sitecore context language cookie)
// note that a route-based language (i.e. /de-DE) will override this default; this is for home.
- if (ssrInitialState && ssrInitialState.context && ssrInitialState.context.language) {
- this.state.defaultLanguage = ssrInitialState.context.language;
- }
-
- // once we initialize the route handler, we've "used up" the SSR data,
- // if it existed, so we want to clear it now that it's in react state.
- // future route changes that might destroy/remount this component should ignore any SSR data.
- // EXCEPTION: Unless we are still SSR-ing. Because SSR can re-render the component twice
- // (once to find GraphQL queries that need to run, the second time to refresh the view with
- // GraphQL query results)
- // We test for SSR by checking for Node-specific process.env variable.
- if (typeof window !== 'undefined') {
- ssrInitialState = null;
+ if (routeData && routeData.context && routeData.context.language) {
+ this.state.defaultLanguage = routeData.context.language;
}
this.componentIsMounted = false;
@@ -72,11 +50,24 @@ export default class RouteHandler extends React.Component {
}
componentDidMount() {
- // if no existing routeData is present (from SSR), get Layout Service fetching the route data
- if (!this.state.routeData) {
+ const routeData = this.extractRouteData();
+
+ // if no existing routeData is present (from SSR), get Layout Service fetching the route data or SSR render is complete
+ if (!routeData || this.props.ssrRenderComplete) {
this.updateRouteData();
}
+ // once we initialize the route handler, we've "used up" the SSR data,
+ // if it existed, so we want to clear it now that it's in react state.
+ // future route changes that might destroy/remount this component should ignore any SSR data.
+ // EXCEPTION: Unless we are still SSR-ing. Because SSR can re-render the component twice
+ // (once to find GraphQL queries that need to run, the second time to refresh the view with
+ // GraphQL query results)
+ // We test for SSR by checking for Node-specific process.env variable.
+ if (typeof window !== "undefined" && !this.props.ssrRenderComplete && this.props.setSsrRenderComplete) {
+ this.props.setSsrRenderComplete(true);
+ }
+
this.componentIsMounted = true;
}
@@ -84,6 +75,19 @@ export default class RouteHandler extends React.Component {
this.componentIsMounted = false;
}
+ extractRouteData = () => {
+ if (!this.props.sitecoreContext) return null;
+
+ const { route, ...context } = this.props.sitecoreContext;
+
+ return {
+ sitecore: {
+ route,
+ context
+ }
+ }
+ }
+
/**
* Loads route data from Sitecore Layout Service into state.routeData
*/
@@ -99,14 +103,16 @@ export default class RouteHandler extends React.Component {
getRouteData(sitecoreRoutePath, language).then((routeData) => {
if (routeData !== null && routeData.sitecore && routeData.sitecore.route) {
// set the sitecore context data and push the new route
- SitecoreContextFactory.setSitecoreContext({
+ this.props.updateSitecoreContext({
route: routeData.sitecore.route,
itemId: routeData.sitecore.route.itemId,
...routeData.sitecore.context,
});
- this.setState({ routeData, notFound: false });
+ this.setState({ notFound: false });
} else {
- this.setState({ routeData, notFound: true });
+ this.setState({ notFound: true }, () =>
+ this.props.updateSitecoreContext(routeData.sitecore.context)
+ )
}
});
}
@@ -156,12 +162,13 @@ export default class RouteHandler extends React.Component {
}
render() {
- const { notFound, routeData } = this.state;
+ const { notFound } = this.state;
+ const routeData = this.extractRouteData();
// no route data for the current route in Sitecore - show not found component.
// Note: this is client-side only 404 handling. Server-side 404 handling is the responsibility
// of the server being used (i.e. node-headless-ssr-proxy and Sitecore intergrated rendering know how to send 404 status codes).
- if (notFound) {
+ if (notFound && routeData) {
return (
@@ -183,14 +190,7 @@ export default class RouteHandler extends React.Component {
}
}
-/**
- * Sets the initial state provided by server-side rendering.
- * Setting this state will bypass initial route data fetch calls.
- * @param {object} ssrState
- */
-export function setServerSideRenderingState(ssrState) {
- ssrInitialState = ssrState;
-}
+export default withSitecoreContext({ updatable: true })(RouteHandler)
/**
* Gets route data from Sitecore. This data is used to construct the component layout for a JSS route.
diff --git a/samples/react/src/index.js b/samples/react/src/index.js
index b9418e068f..2b439d25cd 100644
--- a/samples/react/src/index.js
+++ b/samples/react/src/index.js
@@ -5,7 +5,6 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import AppRoot from './AppRoot';
-import { setServerSideRenderingState } from './RouteHandler';
import GraphQLClientFactory from './lib/GraphQLClientFactory';
import config from './temp/config';
import i18ninit from './i18n';
@@ -35,9 +34,6 @@ if (ssrRawJson) {
__JSS_STATE__ = JSON.parse(ssrRawJson.innerHTML);
}
if (__JSS_STATE__) {
- // push the initial SSR state into the route handler, where it will be used
- setServerSideRenderingState(__JSS_STATE__);
-
// when React initializes from a SSR-based initial state, you need to render with `hydrate` instead of `render`
renderFunction = ReactDOM.hydrate;
@@ -71,6 +67,7 @@ i18ninit(initLanguage).then(() => {
path={window.location.pathname}
Router={BrowserRouter}
graphQLClient={graphQLClient}
+ ssrState={__JSS_STATE__}
/>,
rootElement
);
diff --git a/samples/react/src/lib/SitecoreContextFactory.js b/samples/react/src/lib/SitecoreContextFactory.js
deleted file mode 100644
index 0da5d113a7..0000000000
--- a/samples/react/src/lib/SitecoreContextFactory.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { SitecoreContextFactory } from '@sitecore-jss/sitecore-jss-react';
-
-/*
- The SitecoreContextFactory stores the current Sitecore context for the app.
- For example, whether the page is currently being edited, or the current language.
- Note that the export makes this essentially a singleton, so we can store state in it.
-
- The Sitecore context is generally updated on route change (/src/index.js).
- The `withSitecoreContext()` higher order component from `sitecore-jss-react`
- can be used to access the context from within a component.
-*/
-export default new SitecoreContextFactory();