Skip to content

Commit

Permalink
fix(tailwind-formio): fix form ref events
Browse files Browse the repository at this point in the history
  • Loading branch information
Romakita committed Jul 8, 2022
1 parent bd3e9ba commit e4a267d
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface FormProps<Data = any> extends UseFormHookProps<Data> {
className?: string;
}

export function Form(props: Partial<FormProps>) {
export function Form<Data = any>(props: Partial<FormProps<Data>>) {
const { element } = useForm(props);

return <div data-testid={`formioContainer${props.name || ""}`} ref={element} className={props.className} />;
Expand Down
160 changes: 121 additions & 39 deletions packages/react-formio/src/components/form/form.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,112 @@ export default {
control: {
type: "object"
}
}
},
parameters: {}
},
onPrevPage: {action: "onPrevPage"},
onNextPage: {action: "onNextPage"},
onCancel: {action: "onCancel"},
onChange: {action: "onChange"},
onCustomEvent: {action: "onCustomEvent"},
onComponentChange: {action: "onComponentChange"},
onSubmit: {action: "onSubmit"},
onAsyncSubmit: {action: "onAsyncSubmit"},
onSubmitDone: {action: "onSubmitDone"},
onFormLoad: {action: "onFormLoad"},
onError: {action: "onError"},
onRender: {action: "onRender"},
onAttach: {action: "onAttach"},
onBuild: {action: "onBuild"},
onFocus: {action: "onFocus"},
onBlur: {action: "onBlur"},
onInitialized: {action: "onInitialized"},
onFormReady: {action: "onFormReady"}
}
};

function filter(args: any[]) {
return args
.map((item) => {
if (item && (item._form)) {
return "FormioInstance";
}

if (item && item.component) {
return ["Component", item.component.type, item.component.key].filter(Boolean).join(":");
}

if (item && item.changed) {
return `${item.changed.component.key}(${item.changed.value})`;
}

return item;
});
}

function wrap(args: any) {
return {
...args,
onPrevPage: (...list: any[]) => {
return args.onPrevPage(...filter(list));
},
onNextPage: (...list: any[]) => {
return args.onNextPage(...filter(list));
},
onCancel: (...list: any[]) => {
return args.onCancel(...filter(list));
},
onChange: (...list: any[]) => {
return args.onChange(...filter(list));
},
onCustomEvent: (...list: any[]) => {
return args.onCustomEvent(...filter(list));
},
onComponentChange: (...list: any[]) => {
return args.onComponentChange(...filter(list));
},
onSubmit: (...list: any[]) => {
return args.onSubmit(...filter(list));
},
onAsyncSubmit: (...list: any[]) => {
return args.onAsyncSubmit(...filter(list));
},
onSubmitDone: (...list: any[]) => {
return args.onSubmitDone(...filter(list));
},
onFormLoad: (...list: any[]) => {
return args.onFormLoad(...filter(list));
},
onError: (...list: any[]) => {
return args.onError(...filter(list));
},
onRender: (...list: any[]) => {
return args.onRender(...filter(list));
},
onAttach: (...list: any[]) => {
return args.onAttach(...filter(list));
},
onBuild: (...list: any[]) => {
return args.onBuild(...filter(list));
},
onFocus: (...list: any[]) => {
return args.onFocus(...filter(list));
},
onBlur: (...list: any[]) => {
return args.onBlur(...filter(list));
},
onInitialized: (...list: any[]) => {
return args.onInitialized(...filter(list));
},
onFormReady: (...list: any[]) => {
return args.onFormReady(...filter(list));
}
};
}

export const Sandbox = (args: any) => {
delete args.onRender;
delete args.onComponentChange;
return (
<Form
{...args}
{...wrap(args)}
form={args.form}
onFormReady={(formio) => {
console.log("ready", formio);
}}
options={{template: "tailwind", iconset: "bx"}}
/>
);
Expand All @@ -36,34 +127,27 @@ Sandbox.args = {
};

export const TriggerError = (args: any) => {
delete args.onRender;
delete args.onComponentChange;
const onAsyncSubmit = (submission: Submission) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("server error"));
}, 500);
}).catch((error) => {
error.errors = {
"message": "My custom message about this field",
"type": "custom",
"path": ["firstName"],
"level": "error"
};
throw error;
});
};

return (
<Form
{...args}
<Form<any>
{...wrap(args)}
form={args.form}
onAsyncSubmit={(submission: Submission) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("server error"));
}, 500);
}).catch((error) => {
error.errors = {
"message": "My custom message about this field",
"type": "custom",
"path": ["firstName"],
"level": "error"
}
throw error
});
}}
options={{
hooks: {
template: "tailwind",
iconset: "bx"
}
}}
onAsyncSubmit={onAsyncSubmit}
/>
);
};
Expand Down Expand Up @@ -100,20 +184,18 @@ TriggerError.args = {


export const ReadOnly = (args: any) => {
delete args.onRender;
delete args.onComponentChange;
return (
<Form
{...args}
{...wrap(args)}
options={{template: "tailwind", iconset: "bx", readOnly: args.readOnly}}
form={args.form}
submission={{
data: {
editGrid: [
{currency: 'EUR'}
{currency: "EUR"}
]
}
}}
options={{template: "tailwind", iconset: "bx", readOnly: args.readonly}}
/>
);
};
Expand Down
65 changes: 33 additions & 32 deletions packages/react-formio/src/components/form/useForm.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ export interface UseFormHookProps<Data = any> extends Record<string, any> {
* Data submission
*/
submission?: Submission<Data>;

/// events
onPrevPage?: (obj: FormPageChangeProps<Data>) => void;
onNextPage?: (obj: FormPageChangeProps<Data>) => void;
onCancel?: Function;
onChange?: (submission: ChangedSubmission) => void;
onCustomEvent?: (obj: { type: string; event: string; component: ExtendedComponentSchema; data: any }) => void;
onComponentChange?: (component: ExtendedComponentSchema) => void;
onSubmit?: (submission: Submission<Data>) => void;
onAsyncSubmit?: (submission: Submission<Data>) => Promise<void>;
onAsyncSubmit?: (submission: Submission<Data>) => Promise<any>;
onSubmitDone?: (submission: Submission<Data>) => void;
onFormLoad?: Function;
onError?: (errors: any) => void;
Expand All @@ -52,20 +54,43 @@ export interface UseFormHookProps<Data = any> extends Record<string, any> {
onBlur?: Function;
onInitialized?: Function;
onFormReady?: (formio: Form) => void;
formioform?: any;
}

function useDebounce(event: string, callback: any, events: Map<string, any>) {
useEffect(() => {
callback && events.set(event, callLast(callback, 100));
}, [callback, event, events]);
}

function useEvents(funcs: any) {
const events = useRef<Map<string, any>>(new Map());

const hasEvent = (event: string) => {
return funcs.hasOwnProperty(event) && typeof funcs[event] === "function"
}
const emit = (event: string, ...args: any[]) => {
if (hasEvent(event)) {
const fn = events.current.has(event) ? events.current.get(event) : funcs[event]
return fn(...args);
}
};

useDebounce("onChange", funcs.onChange, events.current);

return {events, emit, hasEvent};
}

export function useForm<Data = any>(props: UseFormHookProps<Data>) {
const {src, form, options = {}, submission, url, ...funcs} = props;
const element = useRef<any>();
const isLoaded = useRef<boolean>();
const instance = useRef<Form>();
const events = useRef<Map<string, any>>(new Map());
const {emit, hasEvent} = useEvents(funcs);

async function customValidation(submission: Submission, callback: (err: Error | null) => void) {
if (events.current.has("onAsyncSubmit")) {
if (hasEvent("onAsyncSubmit")) {
try {
await events.current.get("onAsyncSubmit")(submission);
await emit("onAsyncSubmit", submission, instance.current);
} catch (err) {
callback(err?.errors || err);
}
Expand Down Expand Up @@ -93,27 +118,15 @@ export function useForm<Data = any>(props: UseFormHookProps<Data>) {
}

if (event.startsWith("formio.")) {
const funcName = `on${event.charAt(7).toUpperCase()}${event.slice(8)}`;
const eventName = `on${event.charAt(7).toUpperCase()}${event.slice(8)}`;

if (funcName === "onChange") {
if (eventName === "onChange") {
if (isEqual(get(submission, "data"), args[0].data)) {
return;
}
}

if (
// eslint-disable-next-line no-prototype-builtins
props.hasOwnProperty(funcName) &&
typeof funcs[funcName] === "function"
) {
if (!events.current.has(funcName)) {
const fn = callLast(funcs[funcName], 100);
events.current.set(funcName, fn);
}

instance.current.instance.setAlert("success", "");
events.current.get(funcName)(...args, instance.current);
}
emit(eventName, ...args, instance.current)
}
});

Expand Down Expand Up @@ -172,18 +185,6 @@ export function useForm<Data = any>(props: UseFormHookProps<Data>) {
};
}, []);

useEffect(() => {
props.onSubmit && events.current.set("onSubmit", props.onSubmit);
}, [props.onSubmit, events]);

useEffect(() => {
props.onAsyncSubmit && events.current.set("onAsyncSubmit", props.onAsyncSubmit);
}, [props.onAsyncSubmit, events]);

useEffect(() => {
props.onSubmitDone && events.current.set("onSubmitDone", props.onSubmitDone);
}, [props.onSubmitDone, events]);

return {
element
};
Expand Down
1 change: 0 additions & 1 deletion packages/storybook/.storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ Formio.use(tailwind);
Templates.framework = "tailwind";

export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
Expand Down

0 comments on commit e4a267d

Please sign in to comment.