Skip to content

Commit

Permalink
feat: add onAction API to track and react to state changes
Browse files Browse the repository at this point in the history
  • Loading branch information
nova4u authored Aug 12, 2024
1 parent 60fe631 commit c7007ac
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 26 deletions.
58 changes: 42 additions & 16 deletions apps/docs/pages/docs/api-reference/components/puck.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,23 @@ export function Editor() {

## Props

| Param | Example | Type | Status |
| ----------------------------------- | -------------------------------------- | -------------------------------------------------- | ------------ |
| [`config`](#config) | `config: { components: {} }` | [Config](/docs/api-reference/configuration/config) | Required |
| [`data`](#data) | `data: {}` | [Data](/docs/api-reference/data) | Required |
| [`dnd`](#dnd) | `dnd: {}` | [DndConfig](#dnd-params) | - |
| [`children`](#children) | `children: <Puck.Preview />` | ReactNode | - |
| [`headerPath`](#headerpath) | `headerPath: "/my-page"` | String | - |
| [`headerTitle`](#headertitle) | `headerTitle: "My Page"` | String | - |
| [`iframe`](#iframe) | `iframe: {}` | [IframeConfig](#iframe-params) | - |
| [`initialHistory`](#initialhistory) | `initialHistory: {}` | [InitialHistory](#initialhistory-params) | - |
| [`onChange()`](#onchangedata) | `onChange: (data) => {}` | Function | - |
| [`onPublish()`](#onpublishdata) | `onPublish: async (data) => {}` | Function | - |
| [`overrides`](#overrides) | `overrides: { header: () => <div /> }` | [Overrides](/docs/api-reference/overrides) | Experimental |
| [`plugins`](#plugins) | `plugins: [myPlugin]` | [Plugin\[\]](/docs/api-reference/plugin) | Experimental |
| [`ui`](#ui) | `ui: {leftSideBarVisible: false}` | [AppState.ui](/docs/api-reference/app-state#ui) | - |
| [`viewports`](#viewports) | `viewports: [{ width: 1440 }]` | [Viewport\[\]](#viewport-params) | - |
| Param | Example | Type | Status |
| ----------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | ------------ |
| [`config`](#config) | `config: { components: {} }` | [Config](/docs/api-reference/configuration/config) | Required |
| [`data`](#data) | `data: {}` | [Data](/docs/api-reference/data) | Required |
| [`dnd`](#dnd) | `dnd: {}` | [DndConfig](#dnd-params) | - |
| [`children`](#children) | `children: <Puck.Preview />` | ReactNode | - |
| [`headerPath`](#headerpath) | `headerPath: "/my-page"` | String | - |
| [`headerTitle`](#headertitle) | `headerTitle: "My Page"` | String | - |
| [`iframe`](#iframe) | `iframe: {}` | [IframeConfig](#iframe-params) | - |
| [`initialHistory`](#initialhistory) | `initialHistory: {}` | [InitialHistory](#initialhistory-params) | - |
| [`onAction()`](#onactionaction-appstate-prevappstate) | `onAction: (action, appState, prevAppState) => {}` | Function | - |
| [`onChange()`](#onchangedata) | `onChange: (data) => {}` | Function | - |
| [`onPublish()`](#onpublishdata) | `onPublish: async (data) => {}` | Function | - |
| [`overrides`](#overrides) | `overrides: { header: () => <div /> }` | [Overrides](/docs/api-reference/overrides) | Experimental |
| [`plugins`](#plugins) | `plugins: [myPlugin]` | [Plugin\[\]](/docs/api-reference/plugin) | Experimental |
| [`ui`](#ui) | `ui: {leftSideBarVisible: false}` | [AppState.ui](/docs/api-reference/app-state#ui) | - |
| [`viewports`](#viewports) | `viewports: [{ width: 1440 }]` | [Viewport\[\]](#viewport-params) | - |

## Required props

Expand Down Expand Up @@ -201,6 +202,31 @@ An array of histories to reset the Puck state history state to.

The index of the histories to set the user to.

### `onAction(action, appState, prevAppState)`

Callback that triggers when Puck dispatches an [action](https://puckeditor.com/docs/api-reference/actions), like `insert` or `set`. Use this to track changes, perform side effects, or sync with external systems.

Receives three arguments:

1. `action`: The action that was dispatched
2. `appState`: The new [`AppState`](/docs/api-reference/app-state) after the action was applied
3. `prevAppState`: The previous [`AppState`](/docs/api-reference/app-state) before the action was applied

```tsx {4-8} copy
export function Editor() {
return (
<Puck
onAction={(action, appState, prevAppState) => {
if (action.type === "insert") {
console.log("New component was inserted", appState);
}
}}
// ...
/>
);
}
```

### `onChange(data)`

Callback that triggers when the user makes a change.
Expand Down
5 changes: 4 additions & 1 deletion packages/core/components/Puck/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { DragStart, DragUpdate } from "@measured/dnd";

import type { AppState, Config, Data, UiState } from "../../types/Config";
import type { OnAction } from "../../types/OnAction";
import { Button } from "../Button";

import { Plugin } from "../../types/Plugin";
Expand Down Expand Up @@ -58,6 +59,7 @@ export function Puck<UserConfig extends Config = Config>({
ui: initialUi,
onChange,
onPublish,
onAction,
plugins = [],
overrides = {},
renderHeader,
Expand All @@ -77,6 +79,7 @@ export function Puck<UserConfig extends Config = Config>({
ui?: Partial<UiState>;
onChange?: (data: Data) => void;
onPublish?: (data: Data) => void;
onAction?: OnAction;
plugins?: Plugin[];
overrides?: Partial<Overrides>;
renderHeader?: (props: {
Expand All @@ -103,7 +106,7 @@ export function Puck<UserConfig extends Config = Config>({
const historyStore = useHistoryStore(initialHistory);

const [reducer] = useState(() =>
createReducer<UserConfig>({ config, record: historyStore.record })
createReducer<UserConfig>({ config, record: historyStore.record, onAction })
);

const [initialAppState] = useState<AppState>(() => {
Expand Down
28 changes: 19 additions & 9 deletions packages/core/reducer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AppState, Config } from "../types/Config";
import { reduceData } from "./data";
import { PuckAction, SetAction } from "./actions";
import { reduceUi } from "./state";
import type { OnAction } from "../types/OnAction";

export * from "./actions";
export * from "./data";
Expand All @@ -13,7 +14,8 @@ export type StateReducer = Reducer<AppState, PuckAction>;

const storeInterceptor = (
reducer: StateReducer,
record?: (appState: AppState) => void
record?: (appState: AppState) => void,
onAction?: OnAction
) => {
return (state: AppState, action: PuckAction) => {
const newAppState = reducer(state, action);
Expand All @@ -34,6 +36,8 @@ const storeInterceptor = (
if (record) record(newAppState);
}

onAction?.(action, newAppState, state);

return newAppState;
};
};
Expand All @@ -52,18 +56,24 @@ export const setAction = (state: AppState, action: SetAction) => {
export function createReducer<UserConfig extends Config = Config>({
config,
record,
onAction,
}: {
config: UserConfig;
record?: (appState: AppState) => void;
onAction?: OnAction;
}): StateReducer {
return storeInterceptor((state, action) => {
const data = reduceData(state.data, action, config);
const ui = reduceUi(state.ui, action);
return storeInterceptor(
(state, action) => {
const data = reduceData(state.data, action, config);
const ui = reduceUi(state.ui, action);

if (action.type === "set") {
return setAction(state, action);
}
if (action.type === "set") {
return setAction(state, action);
}

return { data, ui };
}, record);
return { data, ui };
},
record,
onAction
);
}
8 changes: 8 additions & 0 deletions packages/core/types/OnAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { PuckAction } from "../reducer";
import type { AppState } from "./Config";

export type OnAction = (
action: PuckAction,
appState: AppState,
prevAppState: AppState
) => void;

0 comments on commit c7007ac

Please sign in to comment.