-
Notifications
You must be signed in to change notification settings - Fork 556
/
Copy pathwith-data.tsx
124 lines (103 loc) · 3.93 KB
/
with-data.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/* eslint-disable react/jsx-props-no-spreading */
import { ApolloProvider } from '@apollo/react-hooks';
import { getDataFromTree } from '@apollo/react-ssr';
import { NormalizedCacheObject } from 'apollo-cache-inmemory/lib/types';
import { ApolloClient } from 'apollo-client';
import * as cookie from 'cookie';
import { debug } from 'debug';
import { withRouter } from 'next/router';
import * as React from 'react';
import { initApollo } from './init-apollo';
import { IS_SERVER } from '../config';
const logger = debug('app:withData');
logger.log = console.log.bind(console);
function parseCookies(
ctx: any = {},
options = {}
): {
[key: string]: string;
} {
const userCookie = cookie.parse(
ctx.req && ctx.req.headers.cookie ? ctx.req.headers.cookie : '', // document.cookie,
options
);
logger('Parsing cookie: ', userCookie);
return userCookie;
}
export interface IWithDataProps<Q = any> {
serverState: IWithDataServerState;
dataContext: IWithDataContext<Q>;
}
export interface IWithDataServerState {
apollo: { data };
}
export interface IWithDataContext<Q = any> {
query: Q;
pathname: string;
}
export type TComposedComponent<Props> = React.ComponentType<Props> & {
getInitialProps?: (context, apollo) => any;
};
export function withData<TProps extends IWithDataProps>(
ComposedComponent: TComposedComponent<TProps & any>
): React.ComponentType<IWithDataProps> {
return class WithData extends React.Component<IWithDataProps> {
// Note: Apollo should never be used on the server side beyond the initial
// render within `getInitialProps()` (since the entire prop tree
// will be initialized there), meaning the below will only ever be
// executed on the client.
private apollo: ApolloClient<NormalizedCacheObject> = initApollo(
this.props.serverState.apollo.data,
{ getToken: () => parseCookies() }
);
static displayName = `WithData(${ComposedComponent.displayName})`;
static async getInitialProps(context): Promise<{ serverState } | void> {
let serverState: IWithDataServerState = { apollo: { data: {} } };
// Setup a server-side one-time-use apollo client for initial props and
// rendering (on server)
logger('getInitialProps with context:', context);
const apollo = initApollo({}, { getToken: () => parseCookies(context) });
// Evaluate the composed component's getInitialProps()
const childInitialProps = ComposedComponent.getInitialProps
? await ComposedComponent.getInitialProps(context, apollo)
: {};
// Run all GraphQL queries from component tree and extract the resulting data
if (IS_SERVER) {
if (context.res && context.res.finished) {
// When redirecting, the response is finished. No point in continuing to render
return undefined;
}
// Provide the router prop in case a page needs it to render
const router: IWithDataContext = { query: context.query, pathname: context.pathname };
// Run all GraphQL queries
const app = (
<ApolloProvider client={apollo}>
<ComposedComponent router={router} {...childInitialProps} />
</ApolloProvider>
);
await getDataFromTree(app, {
router: { query: context.query, pathname: context.pathname, asPath: context.asPath },
});
serverState = {
apollo: {
// Make sure to only include Apollo's data state
data: apollo.cache.extract(), // Extract query data from the Apollo's store
},
};
}
return {
serverState,
...childInitialProps,
};
}
render(): JSX.Element {
return (
<ApolloProvider client={this.apollo}>
<ComposedComponent {...this.props} />
</ApolloProvider>
);
}
};
}
const compose = (...functions) => (args) => functions.reduceRight((arg, fn) => fn(arg), args);
export const withDataAndRouter = compose(withRouter, withData);