-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Copy pathcreateCell.tsx
151 lines (136 loc) · 4.71 KB
/
createCell.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import type { DocumentNode } from 'graphql'
import type { A } from 'ts-toolbelt'
import { useQuery } from './GraphQLHooksProvider'
interface QueryProps {
query: DocumentNode
children: (result: QueryOperationResult) => React.ReactElement
}
const Query = ({ children, query, ...rest }: QueryProps) => {
const result = useQuery(query, rest)
return result ? children(result) : null
}
export type DataObject = { [key: string]: unknown }
export type CellFailureProps =
| Omit<QueryOperationResult, 'data' | 'loading'>
| { error: Error } // for tests and storybook
export type CellLoadingProps = Omit<
QueryOperationResult,
'error' | 'loading' | 'data'
>
// @MARK not sure about this partial, but we need to do this for tests and storybook
export type CellSuccessProps<TData = any> = Partial<
Omit<QueryOperationResult<TData>, 'error' | 'loading' | 'data'>
> &
A.Compute<TData> // pre-computing makes the types more readable on hover
export interface CreateCellProps<CellProps> {
beforeQuery?: <TProps>(props: TProps) => { variables: TProps }
QUERY: DocumentNode | ((variables: Record<string, unknown>) => DocumentNode)
afterQuery?: (data: DataObject) => DataObject
Loading?: React.FC<CellLoadingProps & Partial<CellProps>>
Failure?: React.FC<CellFailureProps & Partial<CellProps>>
Empty?: React.FC<CellLoadingProps & Partial<CellProps>>
Success: React.FC<CellSuccessProps & Partial<CellProps>>
}
/**
* Is a higher-order-component that executes a GraphQL query and automatically
* manages the lifecycle of that query. If you export named parameters that match
* the required params of `createCell` it will be automatically wrapped in this
* HOC via a babel-plugin.
*
* @param {string} QUERY - The graphQL syntax tree to execute
* @param {function=} beforeQuery - Prepare the variables and options for the query
* @param {function=} afterQuery - Sanitize the data return from graphQL
* @param {Component=} Loading - Loading, render this component
* @param {Component=} Empty - Loading, render this component
* @param {Component=} Failure - Something went wrong, render this component
* @param {Component} Success - Data has loaded, render this component
*
* @example
* ```js
* // IMPLEMENTATION:
* // `src/ExampleComponent/index.js`. This file is automatically dealt with
* in webpack.
*
* import { createCell } from '@redwoodjs/web'
* import * as cell from './ExampleComponent'
*
* export default createCell(cell)
* ```
*
* // USAGE:
* // Now you have a cell component that will handle the lifecycle methods of
* // a query
* import ExampleComponent from 'src/ExampleComponent'
*
* const ThingThatUsesExampleComponent = () => {
* return <div><ExampleComponent /></div>
* }
*/
const isDataNull = (data: DataObject) => {
return dataField(data) === null
}
const isDataEmptyArray = (data: DataObject) => {
const field = dataField(data)
return Array.isArray(field) && field.length === 0
}
const dataField = (data: DataObject) => {
return data[Object.keys(data)[0]]
}
const isEmpty = (data: DataObject) => {
return isDataNull(data) || isDataEmptyArray(data)
}
export function createCell<CellProps = any>({
beforeQuery = (props) => ({
variables: props,
fetchPolicy: 'cache-and-network',
nextFetchPolicy: 'cache-first',
}),
QUERY,
afterQuery = (data) => ({ ...data }),
Loading = () => <>Loading...</>,
Failure,
Empty,
Success,
}: CreateCellProps<CellProps>): React.FC<CellProps> {
if (global.__REDWOOD__PRERENDERING) {
// If its prerendering, render the Cell's Loading component
// and exit early. The apolloclient loading props aren't available here, so 'any'
return (props) => <Loading {...(props as any)} />
}
return (props) => {
const {
children, // eslint-disable-line @typescript-eslint/no-unused-vars
...variables
} = props
return (
<Query
query={
typeof QUERY === 'function' ? QUERY(beforeQuery(variables)) : QUERY
}
{...beforeQuery(variables)}
>
{({ error, loading, data, ...queryRest }) => {
if (error) {
if (Failure) {
return <Failure error={error} {...queryRest} {...props} />
} else {
throw error
}
} else if (loading) {
return <Loading {...queryRest} {...props} />
} else if (data) {
if (typeof Empty !== 'undefined' && isEmpty(data)) {
return <Empty {...queryRest} {...props} />
} else {
return <Success {...afterQuery(data)} {...queryRest} {...props} />
}
} else {
throw new Error(
'Cannot render cell: GraphQL success but `data` is null'
)
}
}}
</Query>
)
}
}