docs(changeset): feat(Former): enhance state management with additional callbacks and state retrieval
This commit is contained in:
5
.changeset/smart-toes-marry.md
Normal file
5
.changeset/smart-toes-marry.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@warkypublic/oranguru': patch
|
||||
---
|
||||
|
||||
feat(Former): enhance state management with additional callbacks and state retrieval
|
||||
@@ -2,13 +2,16 @@ import { newUUID } from '@warkypublic/artemis-kit';
|
||||
import { createSyncStore } from '@warkypublic/zustandsyncstore';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import type { FormerProps, FormerState } from './Former.types';
|
||||
import type { FormerProps, FormerState, FormStateAndProps } from './Former.types';
|
||||
|
||||
const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
FormerState<any> & Partial<FormerProps<any>>,
|
||||
FormerProps<any>
|
||||
>(
|
||||
(set, get) => ({
|
||||
getAllState: () => {
|
||||
return get() as FormStateAndProps<any>;
|
||||
},
|
||||
getState: (key) => {
|
||||
const current = get();
|
||||
return current?.[key];
|
||||
@@ -26,17 +29,19 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
keyValue
|
||||
);
|
||||
if (get().afterGet) {
|
||||
data = await get().afterGet!({ ...data });
|
||||
data = await get().afterGet!({ ...data }, get());
|
||||
}
|
||||
set({ loading: false, values: data });
|
||||
get().onChange?.(data);
|
||||
get().onChange?.(data, get());
|
||||
}
|
||||
if (reset && get().getFormMethods) {
|
||||
const formMethods = get().getFormMethods!();
|
||||
formMethods.reset();
|
||||
}
|
||||
} catch (e) {
|
||||
set({ error: (e as Error)?.message ?? e, loading: false });
|
||||
const errorMessage = (e as Error)?.message ?? e;
|
||||
set({ error: errorMessage, loading: false });
|
||||
get().onError?.(errorMessage, get());
|
||||
}
|
||||
set({ loading: false });
|
||||
},
|
||||
@@ -66,7 +71,7 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
let data = formMethods.getValues();
|
||||
|
||||
if (get().beforeSave) {
|
||||
const newData = await get().beforeSave!(data);
|
||||
const newData = await get().beforeSave!(data, get());
|
||||
data = newData;
|
||||
}
|
||||
|
||||
@@ -76,7 +81,9 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
data = newdata;
|
||||
},
|
||||
(errors) => {
|
||||
set({ error: errors.root?.message || 'Validation errors', loading: false });
|
||||
const errorMessage = errors.root?.message || 'Validation errors';
|
||||
set({ error: errorMessage, loading: false });
|
||||
get().onError?.(errorMessage, get());
|
||||
exit = true;
|
||||
}
|
||||
);
|
||||
@@ -107,21 +114,22 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
data,
|
||||
keyValue
|
||||
);
|
||||
const newData = { ...data, ...savedData }; //Merge what we had. In case the API doesn't return all fields, we don't want to lose them
|
||||
if (get().afterSave) {
|
||||
await get().afterSave!(savedData);
|
||||
await get().afterSave!(newData, get());
|
||||
}
|
||||
set({ loading: false, values: savedData });
|
||||
get().onChange?.(savedData);
|
||||
formMethods.reset(savedData); //reset with saved data to clear dirty state
|
||||
set({ loading: false, values: newData });
|
||||
get().onChange?.(newData, get());
|
||||
formMethods.reset(newData); //reset with saved data to clear dirty state
|
||||
if (!keepOpen) {
|
||||
get().onClose?.(savedData);
|
||||
get().onClose?.(newData);
|
||||
}
|
||||
return savedData;
|
||||
return newData;
|
||||
}
|
||||
|
||||
set({ loading: false, values: data });
|
||||
formMethods.reset(data); //reset with saved data to clear dirty state
|
||||
get().onChange?.(data);
|
||||
get().onChange?.(data, get());
|
||||
if (!keepOpen) {
|
||||
get().onClose?.(data);
|
||||
}
|
||||
@@ -129,7 +137,9 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
return data;
|
||||
}
|
||||
} catch (e) {
|
||||
set({ error: (e as Error)?.message ?? e, loading: false });
|
||||
const errorMessage = (e as Error)?.message ?? e;
|
||||
set({ error: errorMessage, loading: false });
|
||||
get().onError?.(errorMessage, get());
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -181,20 +191,20 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
|
||||
return {
|
||||
id: !id ? newUUID() : id,
|
||||
onClose: () => {
|
||||
onClose: (data?: any) => {
|
||||
const dirty = useStoreApi.getState().dirty;
|
||||
const setState = useStoreApi.getState().setState;
|
||||
if (dirty) {
|
||||
if (confirm('You have unsaved changes. Are you sure you want to close?')) {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
onClose(data);
|
||||
} else {
|
||||
setState('opened', false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
onClose(data);
|
||||
} else {
|
||||
setState('opened', false);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
||||
ref: any
|
||||
) {
|
||||
const {
|
||||
getAllState,
|
||||
getState,
|
||||
onChange,
|
||||
onClose,
|
||||
@@ -26,6 +27,7 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
||||
values,
|
||||
wrapper,
|
||||
} = useFormerStore((state) => ({
|
||||
getAllState: state.getAllState,
|
||||
getState: state.getState,
|
||||
onChange: state.onChange,
|
||||
onClose: state.onClose,
|
||||
@@ -54,7 +56,7 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
||||
() => ({
|
||||
close: async () => {
|
||||
//console.log('close called');
|
||||
onClose?.();
|
||||
onClose?.(getState('values'));
|
||||
setState('opened', false);
|
||||
},
|
||||
getValue: () => {
|
||||
@@ -67,7 +69,7 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
||||
return await save();
|
||||
},
|
||||
setValue: (value: T) => {
|
||||
onChange?.(value);
|
||||
onChange?.(value, getAllState());
|
||||
},
|
||||
show: async () => {
|
||||
//console.log('show called');
|
||||
@@ -78,7 +80,7 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
||||
return await validate();
|
||||
},
|
||||
}),
|
||||
[getState, onChange, validate, save, reset, setState, onClose, onOpen]
|
||||
[getState, getAllState, onChange, validate, save, reset, setState, onClose, onOpen]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -97,7 +99,19 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
||||
return (
|
||||
<FormProvider {...formMethods}>
|
||||
{typeof wrapper === 'function' ? (
|
||||
wrapper(<FormerLayout>{props.children}</FormerLayout>, opened ??false, onClose ?? (() => {setState('opened', false)}), onOpen ?? (() => {setState('opened', true)}), getState)
|
||||
wrapper(
|
||||
<FormerLayout>{props.children}</FormerLayout>,
|
||||
opened ?? false,
|
||||
onClose ??
|
||||
(() => {
|
||||
setState('opened', false);
|
||||
}),
|
||||
onOpen ??
|
||||
(() => {
|
||||
setState('opened', true);
|
||||
}),
|
||||
getState
|
||||
)
|
||||
) : (
|
||||
<FormerLayout>{props.children || null}</FormerLayout>
|
||||
)}
|
||||
|
||||
@@ -15,15 +15,15 @@ export type FormerAPICallType<T extends FieldValues = any> = (
|
||||
) => Promise<T>;
|
||||
|
||||
export interface FormerProps<T extends FieldValues = any> {
|
||||
afterGet?: (data: T) => Promise<T> | void;
|
||||
afterSave?: (data: T) => Promise<void> | void;
|
||||
beforeSave?: (data: T) => Promise<T> | T;
|
||||
afterGet?: (data: T, state: Partial<FormStateAndProps<T>>) => Promise<T> | void;
|
||||
afterSave?: (data: T, state: Partial<FormStateAndProps<T>>) => Promise<void> | void;
|
||||
beforeSave?: (data: T, state: Partial<FormStateAndProps<T>>) => Promise<T> | T;
|
||||
dirty?: boolean;
|
||||
disableHTMlForm?: boolean;
|
||||
id?: string;
|
||||
keepOpen?: boolean;
|
||||
layout?: {
|
||||
buttonArea?: "bottom" | "none" | "top";
|
||||
buttonArea?: 'bottom' | 'none' | 'top';
|
||||
buttonAreaGroupProps?: GroupProps;
|
||||
closeButtonProps?: ButtonProps;
|
||||
closeButtonTitle?: React.ReactNode;
|
||||
@@ -35,10 +35,10 @@ export interface FormerProps<T extends FieldValues = any> {
|
||||
};
|
||||
onAPICall?: FormerAPICallType<T>;
|
||||
onCancel?: () => void;
|
||||
onChange?: (value: T) => void;
|
||||
onClose?: (data?: T) => void;
|
||||
onChange?: (value: T, state: Partial<FormStateAndProps<T>>) => void;
|
||||
onClose?: (data: T | undefined) => void;
|
||||
onConfirmDelete?: (values?: T) => Promise<boolean>;
|
||||
|
||||
onError?: (error: Error | string, state: Partial<FormStateAndProps<T>>) => void;
|
||||
onOpen?: (data?: T) => void;
|
||||
opened?: boolean;
|
||||
primeData?: T;
|
||||
@@ -63,14 +63,15 @@ export interface FormerRef<T extends FieldValues = any> {
|
||||
export type FormerSectionRender<T extends FieldValues = any> = (
|
||||
children: React.ReactNode,
|
||||
opened: boolean,
|
||||
onClose: ((data?: T) => void),
|
||||
onOpen: ((data?: T) => void) ,
|
||||
onClose: (data?: T) => void,
|
||||
onOpen: (data?: T) => void,
|
||||
getState: FormerState<T>['getState']
|
||||
) => React.ReactNode;
|
||||
|
||||
export interface FormerState<T extends FieldValues = any> {
|
||||
deleteConfirmed?: boolean;
|
||||
error?: string;
|
||||
getAllState: () => FormStateAndProps<T>;
|
||||
getFormMethods?: () => UseFormReturn<any, any>;
|
||||
getState: <K extends keyof FormStateAndProps<T>>(key: K) => FormStateAndProps<T>[K];
|
||||
load: (reset?: boolean) => Promise<void>;
|
||||
|
||||
Reference in New Issue
Block a user