UNPKG

3.87 kBJSXView Raw
1import React from 'react';
2import { isEqual } from 'lodash/lang';
3
4// A reactController is also a React component.
5// Define a `render` function at minimum. In this case, we want to
6export default class WrappedPage extends React.Component {
7 static childContextTypes = {
8 api: React.PropTypes.object,
9 app: React.PropTypes.object,
10 cookies: React.PropTypes.object,
11 path: React.PropTypes.string,
12 query: React.PropTypes.object,
13 params: React.PropTypes.string,
14 url: React.PropTypes.string,
15 userAgent: React.PropTypes.string,
16 csrf: React.PropTypes.string,
17 referrer: React.PropTypes.string,
18 env: React.PropTypes.string,
19 };
20
21 getChildContext() {
22 const { api, app, context, cookies } = this.props;
23
24 return {
25 api,
26 app,
27 cookies,
28 path: context.path,
29 query: context.query,
30 params: context.object,
31 url: context.path,
32 userAgent: context.userAgent,
33 csrf: context.csrf,
34 referrer: context.referer,
35 env: context.env,
36 };
37 }
38
39 constructor (props) {
40 super(props);
41
42 this.state = {
43 data: {},
44 meta: {},
45 loaded: !!props.dataCache,
46 finished: false,
47 canceled: false,
48 };
49
50 if (props.dataCache) {
51 let k;
52 for (k in props.dataCache) {
53 props.dataPromises.set(k, Promise.resolve(props.dataCache[k]));
54
55 if (props.dataCache[k] && props.dataCache[k].body) {
56 this.state.data[k] = props.dataCache[k].body;
57 this.state.meta[k] = props.dataCache[k].headers;
58
59 if (this.state.meta[k].tracking && this.track === k) {
60 this.fireTrackingPixel(this.state.meta[k].tracking);
61 }
62 } else {
63 this.state.data[k] = props.dataCache[k];
64 }
65 }
66 }
67 }
68
69 watchProperties(data, dataCache) {
70 // Handle no-data error-page case
71 if (data) {
72 data.forEach((val, key) => {
73 if (!dataCache[key]) {
74 this.watch(key, val);
75 }
76 });
77
78 if (isEqual([...this.props.dataPromises.keys()].sort(), Object.keys(this.state.data).sort())) {
79 this.finish();
80 }
81 }
82 }
83
84 componentDidMount() {
85 this.watchProperties(this.props.dataPromises, this.props.dataCache);
86 }
87
88 watch (property, promise) {
89 promise.then((p) => {
90 this.setState({
91 data: {
92 ...this.state.data,
93 [property]: p && p.body ? p.body : p,
94 },
95 meta: {
96 ...this.state.meta,
97 [property]: p ? p.headers : {},
98 },
99 });
100
101 if (isEqual([...this.props.dataPromises.keys()].sort(), Object.keys(this.state.data).sort())) {
102 this.finish();
103 }
104 }, (e) => {
105 this.props.app.error(e, this.props.ctx, this.props.app);
106 this.props.app.forceRender(this.props.ctx.body, this.props);
107 });
108 }
109
110 finish () {
111 if (this.state.finished === false) {
112 this.props.app.emit('pageview', { ...this.props, data: this.state.data });
113 this.setState({ finished: true });
114 }
115 }
116
117 getPageProps () {
118 const { props, state } = this;
119
120 const pageProps = {
121 ...props,
122 ...state,
123 };
124
125 delete pageProps.dataCache;
126 delete pageProps.Layout;
127 delete pageProps.Page;
128
129 return pageProps;
130 }
131
132 // Use `render` to return the contents of `this.page`. In this example, we're
133 // going to use React, so we'll just return a React element. `render` async.
134 render () {
135 const { Layout, PageLayout=<div />, Page } = this.props;
136 const props = this.getPageProps();
137
138 if (this.props.Layout) {
139 return (
140 <Layout { ...props } key='layout'>
141 <PageLayout { ...props }>
142 <Page { ...props } />
143 </PageLayout>
144 </Layout>
145 );
146 }
147
148 return (
149 <PageLayout { ...props }>
150 <Page { ...props } />
151 </PageLayout>
152 );
153 }
154
155 layout = null;
156 page = null;
157}