-
Notifications
You must be signed in to change notification settings - Fork 336
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add transformProps lib to migrate component props
- Loading branch information
Showing
7 changed files
with
180 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
# Functions | ||
|
||
- [resolveAllData](functions/resolve-all-data) - Utility function to execute all [`resolveData` methods](/docs/api-reference/configuration/component-config#resolvedatadata-params) on a data payload. | ||
- [transformProps](functions/transform-props) - Transform component props stored in the [data payload](/docs/api-reference/data). Use this for migrations, like prop renames. |
50 changes: 50 additions & 0 deletions
50
apps/docs/pages/docs/api-reference/functions/transform-props.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
--- | ||
title: transformProps | ||
--- | ||
|
||
# transformProps | ||
|
||
Transform component props stored in a [Data payload](/docs/api-reference/data). This convenience method can be used for [prop renames and other data migrations](/docs/integrating-puck/prop-migration). | ||
|
||
This method will modify all data in [`content`](/docs/api-reference/data#content) and [`zones`](/docs/api-reference/data#zones). | ||
|
||
```tsx copy showLineNumbers {7-10} | ||
import { transformProps } from "@measured/puck"; | ||
|
||
const data = { | ||
content: [{ type: "HeadingBlock", props: { title: "Hello, world" } }], | ||
}; | ||
|
||
const updatedData = transformProps(data, { | ||
// Rename `title` to `heading` | ||
HeadingBlock: ({ title, ...props }) => ({ heading: title, ...props }), | ||
}); | ||
|
||
console.log(updatedData); | ||
// { content: [{ type: "HeadingBlock", props: { heading: "Hello, world" } }] }; | ||
``` | ||
|
||
## Args | ||
|
||
| Param | Example | Type | | ||
| ------------ | -------------------------------------- | -------------------------------- | | ||
| `data` | `{ content: {}, root: {} }` | [Data](/docs/api-reference/data) | | ||
| `transforms` | `{ HeadingBlock: (props) => (props) }` | Object | | ||
|
||
### `data` | ||
|
||
The [Data payload](/docs/api-reference/data) to be transformed. | ||
|
||
### `transforms` | ||
|
||
An object describing the transform functions for each component defined in your [`config`](/docs/api-reference/configuration/config). | ||
|
||
- `root` is a reserved property, and can be used to update the [`root` component](/docs/api-reference/configuration/config#root) props. | ||
|
||
## Returns | ||
|
||
The updated [Data](/docs/api-reference/data) object. | ||
|
||
## Notes | ||
|
||
- It's important to consider that data may include both components with old data and new data, and write your transform accordingly. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Prop Migration | ||
|
||
Renaming or removing the props passed to your components are considering breaking changes. Any existing [Data](/docs/api-reference/data) payloads that reference these props will be unable to render. | ||
|
||
There are two strategies for dealing with this: | ||
|
||
1. Retaining backwards-compatible props | ||
2. Implementing a prop migration | ||
|
||
## Retaining backwards-compatibility | ||
|
||
The easiest way to avoid breaking changes is to implement your prop changes in a backwards compatible manor: | ||
|
||
```tsx copy showLineNumbers {2} | ||
const config = { | ||
HeadingBlock: ({ title, heading }) => <h1>{heading || title}</h1>, | ||
}; | ||
``` | ||
|
||
## Implementing a prop migration | ||
|
||
It will often be preferrable to update the underlying [Data](/docs/api-reference/data) payload. Puck provides the [`transformProps`](/docs/api-reference/functions/transform-props) utility method to conveniently transform the props for a given component throughout the payload. | ||
|
||
```tsx copy showLineNumbers {15-18} | ||
import { transformProps } from "@measured/puck"; | ||
|
||
const config = { | ||
// Renamed `title` prop to `heading` | ||
HeadingBlock: ({ heading }) => <h1>{heading}</h1>, | ||
}; | ||
|
||
const data = { | ||
content: [ | ||
// HeadingBlock references the legacy `title` prop | ||
{ type: "HeadingBlock", props: { title: "Hello, world" } }, | ||
], | ||
}; | ||
|
||
const updatedData = transformProps(data, { | ||
// Map `heading` to the legacy `title` prop | ||
HeadingBlock: ({ title, ...props }) => ({ heading: title, ...props }), | ||
}); | ||
|
||
console.log(updatedData); | ||
// { content: [{ type: "HeadingBlock", props: { heading: "Hello, world" } }] }; | ||
``` | ||
|
||
You may choose to run this transform every time you render your content, or perform a batch operation against your database. | ||
|
||
```tsx copy showLineNumbers filename="Example showing data being updated before rendering" | ||
import { Puck, Render, transformProps } from "@measured/puck"; | ||
|
||
const transforms = { | ||
HeadingBlock: ({ title, ...props }) => ({ heading: title, ...props }), | ||
}; | ||
|
||
export const MyEditor = ({ data, config }) => ( | ||
<Puck data={transformProps(data, transforms)} config={config} /> | ||
); | ||
|
||
export const MyPage = ({ data, config }) => ( | ||
<Render data={transformProps(data, transforms)} config={config} /> | ||
); | ||
``` | ||
|
||
## Further reading | ||
|
||
- [`transformProps` API reference](/docs/api-reference/functions/transform-props) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,65 @@ | ||
import { CurrentData, LegacyData } from "../types/Config"; | ||
import { | ||
CurrentData, | ||
Data, | ||
DefaultComponentProps, | ||
LegacyData, | ||
} from "../types/Config"; | ||
import { dataTransforms } from "./data-transforms"; | ||
|
||
export type DataTransform = ( | ||
props: LegacyData & { [key: string]: any } | ||
) => CurrentData; | ||
|
||
type PropTransform<Components = any> = { | ||
[ComponentName in keyof Components]: ( | ||
props: Components[ComponentName] & { [key: string]: any } | ||
) => Components[ComponentName]; | ||
}; | ||
|
||
export const runTransforms = ( | ||
data: LegacyData, | ||
propTransforms: PropTransform[] | ||
): CurrentData => { | ||
const afterDataTransforms = dataTransforms.reduce( | ||
// type TransformFn = | ||
type PropTransform< | ||
Props extends DefaultComponentProps = DefaultComponentProps, | ||
RootProps extends DefaultComponentProps = DefaultComponentProps | ||
> = Partial< | ||
{ | ||
[ComponentName in keyof Props]: ( | ||
props: Props[ComponentName] & { [key: string]: any } | ||
) => Props[ComponentName]; | ||
} & { root: (props: RootProps & { [key: string]: any }) => RootProps } | ||
>; | ||
|
||
export function transformData(data: Data): CurrentData { | ||
return dataTransforms?.reduce( | ||
(acc, dataTransform) => dataTransform(acc), | ||
data | ||
) as CurrentData; | ||
} | ||
|
||
export function transformProps< | ||
Props extends DefaultComponentProps = DefaultComponentProps, | ||
RootProps extends DefaultComponentProps = DefaultComponentProps | ||
>(data: Data, propTransforms: PropTransform<Props, RootProps>): CurrentData { | ||
const afterDataTransform = transformData(data); | ||
|
||
const mapItem = (item) => { | ||
if (propTransforms[item.type]) { | ||
return { | ||
...item, | ||
props: propTransforms[item.type]!(item.props as any), | ||
}; | ||
} | ||
|
||
return item; | ||
}; | ||
|
||
const afterPropTransforms: CurrentData = { | ||
...data, | ||
root: propTransforms["root"] | ||
? propTransforms["root"](afterDataTransform.root.props as any) | ||
: afterDataTransform.root, | ||
content: data.content.map(mapItem), | ||
zones: Object.keys(data.zones || {}).reduce( | ||
(acc, zoneKey) => ({ | ||
...acc, | ||
[zoneKey]: data.zones![zoneKey].map(mapItem), | ||
}), | ||
{} | ||
), | ||
}; | ||
|
||
return afterDataTransforms; | ||
}; | ||
return afterPropTransforms; | ||
} |