Skip to content

Commit

Permalink
Static typing for cells
Browse files Browse the repository at this point in the history
This PR is the first step towards implementing #2205. It allows users to declare the prop types their cells expect.

It also renames `withCell` to `createCell`, for the reasons I outlined in #2205 (comment). `createCell` feels like a more parsimonious name to me but I can back that change out if others disagree.

It appears that the babel transform for wrapping cells already checks for a default export and bails out if one exists, so this shouldn't require any changes there.

The major remaining task is updating the cell generator to add a `export default createCell` line at the end of each cell in Typescript. I'd appreciate more input on whether this is a change we're fine making on both the TS and JS sides, or whether we'll need two templates. Do we have multiple templates anywhere else?
  • Loading branch information
corbt committed Apr 4, 2021
1 parent 1ebf848 commit f79cf4f
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 22 deletions.
18 changes: 9 additions & 9 deletions packages/core/src/babelPlugins/babel-plugin-redwood-cell.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { PluginObj, types } from '@babel/core'

// This wraps a file that has a suffix of `Cell` in Redwood's `withCell` higher
// This wraps a file that has a suffix of `Cell` in Redwood's `createCell` higher
// order component. The HOC deals with the lifecycle methods during a GraphQL query.
//
// ```js
// import { withCell } from '@redwoodjs/web'
// import { createCell } from '@redwoodjs/web'
// <YOUR CODE>
// export default withCell({ QUERY, Loading, Succes, Failure, Empty, beforeQuery, afterQuery })
// export default createCell({ QUERY, Loading, Success, Failure, Empty, beforeQuery, afterQuery })
// ```

// A cell can export the declarations below.
Expand All @@ -23,7 +23,7 @@ const EXPECTED_EXPORTS_FROM_CELL = [
export default function ({ types: t }: { types: typeof types }): PluginObj {
// This array will
// - collect exports from the Cell file during ExportNamedDeclaration
// - collected exports will then be passed to `withCell`
// - collected exports will then be passed to `createCell`
// - be cleared after Program exit to prepare for the next file
let exportNames: string[] = []
let hasDefaultExport = false
Expand Down Expand Up @@ -65,24 +65,24 @@ export default function ({ types: t }: { types: typeof types }): PluginObj {
}

// Insert at the top of the file:
// + import { withCell } from '@redwoodjs/web'
// + import { createCell } from '@redwoodjs/web'
path.node.body.unshift(
t.importDeclaration(
[
t.importSpecifier(
t.identifier('withCell'),
t.identifier('withCell')
t.identifier('createCell'),
t.identifier('createCell')
),
],
t.stringLiteral('@redwoodjs/web')
)
)

// Insert at the bottom of the file:
// + export default withCell({ QUERY?, Loading?, Succes?, Failure?, Empty?, beforeQuery?, afterQuery? })
// + export default createCell({ QUERY?, Loading?, Succes?, Failure?, Empty?, beforeQuery?, afterQuery? })
path.node.body.push(
t.exportDefaultDeclaration(
t.callExpression(t.identifier('withCell'), [
t.callExpression(t.identifier('createCell'), [
t.objectExpression(
exportNames.map((name) =>
t.objectProperty(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ export type CellSuccessStateComponent =
| Omit<OperationResult, 'error' | 'loading' | 'data'>
| DataObject

export interface WithCellProps {
export interface CreateCellProps<CellProps> {
beforeQuery?: <TProps>(props: TProps) => { variables: TProps }
QUERY: DocumentNode | ((variables: Record<string, unknown>) => DocumentNode)
afterQuery?: (data: DataObject) => DataObject
Loading?: React.FC<CellLoadingEmptyStateComponent>
Failure?: React.FC<CellFailureStateComponent>
Empty?: React.FC<CellLoadingEmptyStateComponent>
Success: React.FC<CellSuccessStateComponent>
Loading?: React.FC<CellLoadingEmptyStateComponent & Partial<CellProps>>
Failure?: React.FC<CellFailureStateComponent & Partial<CellProps>>
Empty?: React.FC<CellLoadingEmptyStateComponent & Partial<CellProps>>
Success: React.FC<CellSuccessStateComponent & 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 `withCell` it will be automatically wrapped in this
* 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
Expand All @@ -56,10 +56,10 @@ export interface WithCellProps {
* // `src/ExampleComponent/index.js`. This file is automatically dealt with
* in webpack.
*
* import { withCell } from '@redwoodjs/web'
* import { createCell } from '@redwoodjs/web'
* import * as cell from './ExampleComponent'
*
* export default withCell(cell)
* export default createCell(cell)
* ```
*
* // USAGE:
Expand Down Expand Up @@ -89,7 +89,7 @@ const isEmpty = (data: DataObject) => {
return isDataNull(data) || isDataEmptyArray(data)
}

export const withCell = ({
export function createCell<CellProps = any>({
beforeQuery = (props) => ({
variables: props,
fetchPolicy: 'cache-and-network',
Expand All @@ -101,13 +101,13 @@ export const withCell = ({
Failure,
Empty,
Success,
}: WithCellProps) => {
}: CreateCellProps<CellProps>): React.FC<CellProps> {
// If its prerendering, render the Cell's Loading component
if (global.__REDWOOD__PRERENDERING) {
return (props: Record<string, unknown>) => <Loading {...props} />
return (props) => <Loading {...props} />
}

return (props: Record<string, unknown>) => {
return (props) => {
const {
children, // eslint-disable-line @typescript-eslint/no-unused-vars
...variables
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export {
useMutation,
} from './components/GraphQLHooksProvider'

export { withCell } from './components/withCellHOC'
export { createCell } from './components/createCell'

// TODO: Remove these in v.10, people can import from `@redwoodjs/web/toast`
// deprecated
Expand Down

0 comments on commit f79cf4f

Please sign in to comment.