Skip to content

Commit

Permalink
fix: improve stability of resolveFields API
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvxd committed Jan 18, 2025
1 parent 62d9972 commit 5c60d6a
Show file tree
Hide file tree
Showing 6 changed files with 662 additions and 149 deletions.
146 changes: 4 additions & 142 deletions packages/core/components/Puck/components/Fields/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,18 @@ import {
replaceAction,
setAction,
} from "../../../../reducer";
import { ComponentData, RootData, UiState } from "../../../../types";
import type { Field, Fields as FieldsType } from "../../../../types";
import { UiState } from "../../../../types";
import { AutoFieldPrivate } from "../../../AutoField";
import { useAppContext } from "../../context";

import styles from "./styles.module.css";
import { getClassNameFactory } from "../../../../lib";
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
import { ReactNode, useMemo } from "react";
import { ItemSelector } from "../../../../lib/get-item";
import { getChanged } from "../../../../lib/get-changed";
import { useParent } from "../../../../lib/use-parent";
import { useResolvedFields } from "../../../../lib/use-resolved-fields";

const getClassName = getClassNameFactory("PuckFields", styles);

const defaultPageFields: Record<string, Field> = {
title: { type: "text" },
};

const DefaultFields = ({
children,
}: {
Expand All @@ -34,139 +28,7 @@ const DefaultFields = ({
return <>{children}</>;
};

type ComponentOrRootData = Omit<ComponentData<any>, "type">;

const useResolvedFields = (): [FieldsType, boolean] => {
const { selectedItem, state, config } = useAppContext();
const parent = useParent();

const { data } = state;

const rootFields = config.root?.fields || defaultPageFields;

const componentConfig = selectedItem
? config.components[selectedItem.type]
: null;

const defaultFields = useMemo(
() =>
(selectedItem
? (componentConfig?.fields as Record<string, Field<any>>)
: rootFields) || {},
[selectedItem, rootFields, componentConfig?.fields]
);

// DEPRECATED
const rootProps = data.root.props || data.root;

const [lastSelectedData, setLastSelectedData] = useState<
Partial<ComponentOrRootData>
>({});
const [resolvedFields, setResolvedFields] = useState(defaultFields);
const [fieldsLoading, setFieldsLoading] = useState(false);

const defaultResolveFields = (
_componentData: ComponentOrRootData,
_params: {
fields: FieldsType;
lastData: Partial<ComponentOrRootData> | null;
lastFields: FieldsType;
changed: Record<string, boolean>;
}
) => defaultFields;

const componentData: ComponentOrRootData = selectedItem
? selectedItem
: { props: rootProps, readOnly: data.root.readOnly };

const hasComponentResolver = selectedItem && componentConfig?.resolveFields;
const hasRootResolver = !selectedItem && config.root?.resolveFields;
const hasResolver = hasComponentResolver || hasRootResolver;

const resolveFields = useCallback(
async (fields: FieldsType = {}) => {
const lastData =
lastSelectedData.props?.id === componentData.props.id
? lastSelectedData
: null;

const changed = getChanged(componentData, lastData);

setLastSelectedData(componentData);

if (hasComponentResolver) {
return await componentConfig!.resolveFields!(
componentData as ComponentData,
{
changed,
fields,
lastFields: resolvedFields,
lastData: lastData as ComponentData,
appState: state,
parent,
}
);
}

if (hasRootResolver) {
return await config.root!.resolveFields!(componentData, {
changed,
fields,
lastFields: resolvedFields,
lastData: lastData as RootData,
appState: state,
parent,
});
}

return defaultResolveFields(componentData, {
changed,
fields,
lastFields: resolvedFields,
lastData,
});
},
[data, config, componentData, selectedItem, resolvedFields, state, parent]
);

const [hasParent, setHasParent] = useState(false);

useEffect(() => {
setHasParent(!!parent);
}, [parent]);

useEffect(() => {
// Must either be in default zone, or have parent
if (
!state.ui.itemSelector?.zone ||
state.ui.itemSelector?.zone === "default-zone" ||
hasParent
) {
if (hasResolver) {
setFieldsLoading(true);

resolveFields(defaultFields).then((fields) => {
setResolvedFields(fields || {});

setFieldsLoading(false);
});
} else {
setResolvedFields(defaultFields);
}
}
}, [
data,
defaultFields,
state.ui.itemSelector,
selectedItem,
hasResolver,
hasParent,
]);

return [resolvedFields, fieldsLoading];
};

export const Fields = () => {
export const Fields = ({ wrapFields = true }: { wrapFields?: boolean }) => {
const {
selectedItem,
state,
Expand Down
Loading

0 comments on commit 5c60d6a

Please sign in to comment.