Skip to content

Commit

Permalink
fix: fix setValues api bug
Browse files Browse the repository at this point in the history
  • Loading branch information
97vack committed Dec 30, 2023
1 parent af373d2 commit 8038d81
Show file tree
Hide file tree
Showing 17 changed files with 3,113 additions and 2,734 deletions.
12 changes: 0 additions & 12 deletions .dumi/metadata/apis/docs_apiDemos_FormApi.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,6 @@
"param": "bindId"
}
},
"setValues": {
"defaultValue": null,
"name": "setValues",
"type": {
"name": "(values: Record<string, any>) => void"
},
"tags": {
"description": "Set values uniformly for form items",
"localKey": "API.form.setValues.desc",
"param": "bindId"
}
},
"setError": {
"defaultValue": null,
"name": "setError",
Expand Down
24 changes: 12 additions & 12 deletions .dumi/metadata/apis/docs_apiDemos_useFormReturnType.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@
"resetType": "Function"
}
},
"setValues": {
"defaultValue": null,
"name": "setValues",
"type": {
"name": "(values: Record<string, any>) => void"
},
"tags": {
"description": "Set values uniformly for form items",
"localKey": "API.form.setValues.desc",
"param": "values"
}
},
"clearValidate": {
"defaultValue": null,
"name": "clearValidate",
Expand Down Expand Up @@ -107,18 +119,6 @@
"param": "bindId"
}
},
"setValues": {
"defaultValue": null,
"name": "setValues",
"type": {
"name": "(values: Record<string, any>) => void"
},
"tags": {
"description": "Set values uniformly for form items",
"localKey": "API.form.setValues.desc",
"param": "bindId"
}
},
"setError": {
"defaultValue": null,
"name": "setError",
Expand Down
1 change: 1 addition & 0 deletions .dumi/theme/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"API.form.global.props.GetContentOptions.isError.desc": "The verification status of the current form item. It is true if the verification fails.",
"API.form.global.props.GetContentOptions.attrs.desc": "The basic properties of the control. During form item rendering, the above properties will be passed to your UI control. If your UI control is not based on these properties, please use FormItem to encapsulate your UI.",
"API.form.global.props.ApiEffectOptions.uid.desc": "The current form item uid.",
"API.form.global.props.ApiEffectOptions.refreshValue.desc": "Manually refresh the value inside the form item.",
"API.form.global.props.ContextProps.mounted.desc": "The life cycle executed after each form item is mounted. Will only be executed once during rendering. You can perform dependency collection or initialization operations on form items here. Callback an options object, which contains the bindId of the form item and the uid of the form item.",
"API.form.global.props.ContextProps.apiEffect.desc": "The method triggered after the form item api or bindId changes will be executed once during initialization. Generally there will be no changes, and the API can be collected here.",
"API.form.global.props.ContextProps.updated.desc": "A method that is triggered when the form item monitors the value of the control. <strong>This method will only be executed when manually input</strong>. Customized external form model data can be assigned here. to achieve a controlled effect.",
Expand Down
1 change: 1 addition & 0 deletions .dumi/theme/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"API.form.global.props.GetContentOptions.isError.desc": "当前表单项的校验状态,有校验不通过的情况下为true。",
"API.form.global.props.GetContentOptions.attrs.desc": "控件基本属性,在表单项渲染中,会将如上属性传递给你的UI控件。如果你的UI控件不是基于这些属性,请使用FormItem针对你的UI做一层封装。",
"API.form.global.props.ApiEffectOptions.uid.desc": "当前表单项uid。",
"API.form.global.props.ApiEffectOptions.refreshValue.desc": "手动重新刷新表单项内部的value值。",
"API.form.global.props.ContextProps.mounted.desc": "每个表单项挂载之后执行的生命周期。在渲染中只会执行一次。可以在这里对表单项执行依赖收集或者初始化操作。回调出一个options对象,里面包含该表单项的bindId和该表单项的uid。",
"API.form.global.props.ContextProps.apiEffect.desc": "表单项api或者bindId发生变动后触发的方法,初始化会执行一次。一般不会发生变动,可以在这里对api进行收集。",
"API.form.global.props.ContextProps.updated.desc": "表单项监听控件value发生改变时触发的方法,<strong>只有当手动去输入的时候,这个方法才会执行</strong>。可以在这里对自定义的外部表单model数据进行赋值。以达到受控的效果。",
Expand Down
3 changes: 2 additions & 1 deletion docs/demos/_custom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export default function App() {
useEffect(() => {
const values = { name: 'name', age: 'age' };
model.current = values;
formRef.current?.setValues(values);
formRef.current?.setValue('name', 'name');
formRef.current?.setValue('age', 'age');
}, []);

const renderName = (
Expand Down
11 changes: 6 additions & 5 deletions src/template/FormItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ export const FormItem = React.forwardRef<

const { mounted, destroy, apiEffect, updated } = contextProps || {};

const { apis, triggers, globalDatas } = useFormItemController({
...assigns,
});
const { apis, exportEffectApis, triggers, globalDatas } =
useFormItemController({
...assigns,
});

const { uid, subscribe, formUtil, bindId: _bindId } = globalDatas;

Expand All @@ -70,9 +71,9 @@ export const FormItem = React.forwardRef<
apiEffect?.({
uid,
bindId,
...apis,
...exportEffectApis,
});
}, [...Object.values(apis), bindId]);
}, [...Object.values(exportEffectApis), bindId]);

useImperativeHandle(ref, () => ({
...apis,
Expand Down
17 changes: 11 additions & 6 deletions src/types/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ export namespace Apis {
* @param bindId
*/
setValue: (bindId: any, value: any) => void;
/**
* @description Set values uniformly for form items
* @localKey API.form.setValues.desc
* @param bindId
*/
setValues: (values: Record<string, any>) => void;
// /**
// * @description Set values uniformly for form items
// * @localKey API.form.setValues.desc
// * @param bindId
// */
// setValues: (values: Record<string, any>) => void;
/**
* @description Manually calling to set the error message will not trigger the onError event
* @localKey API.formItem.setError.desc
Expand Down Expand Up @@ -223,6 +223,11 @@ export namespace GlobalProps {
* @description The current form item uid.
*/
uid: string;
// /**
// * @description Manually refresh the value inside the form item
// * @localKey API.form.global.props.ApiEffectOptions.refreshValue.desc
// */
// refreshValue: () => void;
} & Omit<Apis.FormApis, 'setValues'>;

/**
Expand Down
20 changes: 3 additions & 17 deletions src/use/useContextApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useState } from 'react';
import { Apis, GlobalProps } from 'react-form-simple/types/form';
import { useCallback } from 'react';
import type { Apis, GlobalProps } from 'react-form-simple/types/form';
import { isMeaningful } from 'react-form-simple/utils/util';
import useControllerRef from './useControllerRef';

Expand All @@ -10,8 +10,6 @@ export const useContextApi = () => {
});
const { apis, bindIdApis } = globalDatas;

const [, setState] = useState({});

const executeMethodFromApis = useCallback(
(bindId: Apis.ValidateBindIds, methodName: string, ...args: any[]) => {
if (!isMeaningful(bindId)) {
Expand Down Expand Up @@ -66,24 +64,12 @@ export const useContextApi = () => {
api.setValue(value);
}
},
setValues(values) {
if (!values || typeof values !== 'object') return;
Object.entries(values).forEach(([key, value]) => {
overlayApis.setValue(key, value);
});
},
setError(bindId, message) {
executeMethodFromApis(bindId, 'setError', message);
},
});

const useFormExtraApis = useControllerRef({
setState() {
setState({});
},
});

return { globalDatas, contextProps, overlayApis, useFormExtraApis };
return { globalDatas, contextProps, overlayApis };
};

export default useContextApi;
14 changes: 7 additions & 7 deletions src/use/useForm.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { debounce } from 'lodash';
import { useCallback, useRef } from 'react';
import { createObserverForm } from 'react-form-simple';
import { GlobalProps } from 'react-form-simple/types/form';
import { UseFormNamespace } from 'react-form-simple/types/use';
import type { GlobalProps } from 'react-form-simple/types/form';
import type { UseFormNamespace } from 'react-form-simple/types/use';
import { updateProxyValue } from 'react-form-simple/utils/controller';
import { useContextApi } from './useContextApi';
import { useFormExtraApis } from './useFormExtraApis';
import { useRender } from './useRender';
import { usePrivateSubscribe } from './useSubscribe';
import { usePrivateWatch } from './useWatch';
Expand All @@ -15,8 +16,7 @@ const useForm = <T extends Record<string, any>>(
) => {
const proxyTarget = useRef(model || {});

const { contextProps, overlayApis, useFormExtraApis, globalDatas } =
useContextApi();
const { contextProps, overlayApis, globalDatas } = useContextApi();

const { useWatch, watchInstance } = usePrivateWatch({ model });

Expand Down Expand Up @@ -49,15 +49,15 @@ const useForm = <T extends Record<string, any>>(
model: proxymodel as T,
});

const _contextProps: GlobalProps.ContextProps = {
const _contextProps = useRef<GlobalProps.ContextProps>({
...contextProps,
updated({ bindId, value }) {
updateProxyValue(proxymodel, bindId, value);
},
reset({ bindId, value }) {
updateProxyValue(proxymodel, bindId, value);
},
};
}).current;

const { render, set } = useRender({
...config,
Expand All @@ -72,8 +72,8 @@ const useForm = <T extends Record<string, any>>(
render,
useSubscribe,
useWatch,
setState: useFormExtraApis.setState,
...overlayApis,
...useFormExtraApis({ model: proxymodel }),
...createObserverMap,
};
};
Expand Down
25 changes: 25 additions & 0 deletions src/use/useFormExtraApis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useRef, useState } from 'react';
import type { UseFormReturnType } from 'react-form-simple';
import {
replaceTarget,
updateProxyValue,
} from 'react-form-simple/utils/controller';

export const useFormExtraApis = <T>({ model }: { model: T }) => {
const [, setState] = useState({});
const extraApis = useRef<
Pick<UseFormReturnType, 'setState' | 'setValues' | 'setValue'>
>({
setState() {
setState({});
},
setValues(values) {
replaceTarget(model as object, values);
},
setValue(bindId, message) {
updateProxyValue(model, bindId, message);
},
}).current;

return extraApis;
};
11 changes: 7 additions & 4 deletions src/use/useFormItemController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import { getUuid, isMeaningful } from 'react-form-simple/utils/util';

const defaultValueSymbol = Symbol('defaultValue');

export const useFormItemController = (
options: GlobalProps.FormItemProps,
) => {
export const useFormItemController = (options: GlobalProps.FormItemProps) => {
const {
onError,
defaultValue = defaultValueSymbol,
Expand Down Expand Up @@ -56,7 +54,7 @@ export const useFormItemController = (
};
}, []);

const onErrorDebounce= useMemo<typeof onError | null>(
const onErrorDebounce = useMemo<typeof onError | null>(
() => (onError && typeof onError === 'function' ? debounce(onError) : null),
[onError],
);
Expand Down Expand Up @@ -145,8 +143,13 @@ export const useFormItemController = (
},
});

const exportEffectApis = useControllerRef({
...exportApis,
});

return {
apis: exportApis,
exportEffectApis,
triggers,
globalDatas,
bindId: globalDatas.bindId,
Expand Down
4 changes: 4 additions & 0 deletions src/use/useRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export const useRender = (config: UseRenderNamespace.UseRenderOptions) => {

const defaultValue = getProxyValue(model, bindId);

if (bindId === 'select11') {
console.log(defaultValue, bindId);
}

const otherProps: Record<string, any> = {};
if (isMeaningful(key)) {
otherProps['key'] = key;
Expand Down
2 changes: 1 addition & 1 deletion src/use/useSubscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const usePrivateSubscribe = <T extends Record<string, any>>(options: {
const subscribes = useControllerRef(subscribeObject());

const useSubscribe: UseSubscribeNamespace.UseSubscribe<T> = (cb) => {
const [state, setState] = useState();
const [state, setState] = useState<any>();
useEffect(() => {
subscribes.set(() => {
setState(cb({ model: cloneDeep(model) }));
Expand Down
2 changes: 1 addition & 1 deletion src/utils/FormUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class FormUtil {
readonly formvalidate: Formvalidate = new Formvalidate();
bindId: GlobalProps.BindId;
trigger: Trigger = [];
defaultValue: any = {};
defaultValue: any = null;
rules: Rules = [];
constructor(public configOptions?: FormUtilTypes) {
this.defaultValue = this.configOptions?.defaultValue;
Expand Down
63 changes: 50 additions & 13 deletions src/utils/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,58 @@ export type ObserverCb = { path: string; value: any };

export const toTarget = (proxy: any) => cloneDeep(proxy);

export const replaceTarget = (
proxy: any,
obj: any,
isExitNew: boolean = false,
) => {
if (!isObject(obj)) return proxy;
const cloneObj = cloneDeep(obj);
for (const [key, value] of Object.entries(cloneObj)) {
if (isExitNew) {
Reflect.set(proxy, key, value);
} else if (Reflect.has(proxy, key)) {
Reflect.set(proxy, key, value);
interface ProxyObject {
[key: string]: any;
}
export const replaceTarget = (proxyObject: any, values: any) => {
if (!isObject(values)) return proxyObject;
function setNestedValue(obj: ProxyObject, keys: string[], value: any): void {
const lastKey = keys.pop();
let currentObj = obj;

keys.forEach((key) => {
if (!currentObj[key] || typeof currentObj[key] !== 'object') {
currentObj[key] = {};
}
currentObj = currentObj[key];
});

currentObj[lastKey as string] = value;
}

function processArray(obj: ProxyObject, value: any[], path: string[]): void {
obj[path[0]] = value.map((item) => {
if (typeof item === 'object' && item !== null && !Array.isArray(item)) {
const nestedObj: ProxyObject = {};
processValues(nestedObj, item);
return nestedObj;
}
return item;
});
}

function processValues(
obj: ProxyObject,
values: Record<string, any>,
currentPath: string[] = [],
): void {
for (const [key, value] of Object.entries(values)) {
const path = [...currentPath, key];

if (Array.isArray(value)) {
processArray(obj, value, path);
} else if (typeof value === 'object' && value !== null) {
processValues(obj, value, path);
} else {
setNestedValue(obj, path, value);
}
}
}
return proxy;

// 调用递归处理函数
processValues(proxyObject, values);

return proxyObject;
};

export const getProxyValue = (
Expand Down
Loading

0 comments on commit 8038d81

Please sign in to comment.