Skip to content

Commit 90b8298

Browse files
authored
chore: Internal provide ListContext for auto access (#336)
* refactor: Cache List * test: test case
1 parent 1a46d7a commit 90b8298

File tree

3 files changed

+167
-114
lines changed

3 files changed

+167
-114
lines changed

src/List.tsx

Lines changed: 114 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as React from 'react';
22
import warning from 'rc-util/lib/warning';
3-
import { InternalNamePath, NamePath, StoreValue, ValidatorRule, Meta } from './interface';
3+
import type { InternalNamePath, NamePath, StoreValue, ValidatorRule, Meta } from './interface';
44
import FieldContext from './FieldContext';
55
import Field from './Field';
66
import { move, getNamePath } from './utils/valueUtil';
7+
import type { ListContextProps } from './ListContext';
8+
import ListContext from './ListContext';
79

810
export interface ListField {
911
name: number;
@@ -43,15 +45,31 @@ const List: React.FunctionComponent<ListProps> = ({
4345
});
4446
const keyManager = keyRef.current;
4547

48+
const prefixName: InternalNamePath = React.useMemo(() => {
49+
const parentPrefixName = getNamePath(context.prefixName) || [];
50+
return [...parentPrefixName, ...getNamePath(name)];
51+
}, [context.prefixName, name]);
52+
53+
const fieldContext = React.useMemo(() => ({ ...context, prefixName }), [context, prefixName]);
54+
55+
// List context
56+
const listContext = React.useMemo<ListContextProps>(
57+
() => ({
58+
getKey: (namePath: InternalNamePath) => {
59+
const len = prefixName.length;
60+
const pathName = namePath[len];
61+
return keyManager.keys[pathName];
62+
},
63+
}),
64+
[prefixName],
65+
);
66+
4667
// User should not pass `children` as other type.
4768
if (typeof children !== 'function') {
4869
warning(false, 'Form.List only accepts function as children.');
4970
return null;
5071
}
5172

52-
const parentPrefixName = getNamePath(context.prefixName) || [];
53-
const prefixName: InternalNamePath = [...parentPrefixName, ...getNamePath(name)];
54-
5573
const shouldUpdate = (prevValue: StoreValue, nextValue: StoreValue, { source }) => {
5674
if (source === 'internal') {
5775
return false;
@@ -60,93 +78,98 @@ const List: React.FunctionComponent<ListProps> = ({
6078
};
6179

6280
return (
63-
<FieldContext.Provider value={{ ...context, prefixName }}>
64-
<Field
65-
name={[]}
66-
shouldUpdate={shouldUpdate}
67-
rules={rules}
68-
validateTrigger={validateTrigger}
69-
initialValue={initialValue}
70-
isList
71-
>
72-
{({ value = [], onChange }, meta) => {
73-
const { getFieldValue } = context;
74-
const getNewValue = () => {
75-
const values = getFieldValue(prefixName || []) as StoreValue[];
76-
return values || [];
77-
};
78-
/**
79-
* Always get latest value in case user update fields by `form` api.
80-
*/
81-
const operations: ListOperations = {
82-
add: (defaultValue, index?: number) => {
83-
// Mapping keys
84-
const newValue = getNewValue();
85-
86-
if (index >= 0 && index <= newValue.length) {
87-
keyManager.keys = [
88-
...keyManager.keys.slice(0, index),
89-
keyManager.id,
90-
...keyManager.keys.slice(index),
91-
];
92-
onChange([...newValue.slice(0, index), defaultValue, ...newValue.slice(index)]);
93-
} else {
94-
if (
95-
process.env.NODE_ENV !== 'production' &&
96-
(index < 0 || index > newValue.length)
97-
) {
98-
warning(
99-
false,
100-
'The second parameter of the add function should be a valid positive number.',
101-
);
81+
<ListContext.Provider value={listContext}>
82+
<FieldContext.Provider value={fieldContext}>
83+
<Field
84+
name={[]}
85+
shouldUpdate={shouldUpdate}
86+
rules={rules}
87+
validateTrigger={validateTrigger}
88+
initialValue={initialValue}
89+
isList
90+
>
91+
{({ value = [], onChange }, meta) => {
92+
const { getFieldValue } = context;
93+
const getNewValue = () => {
94+
const values = getFieldValue(prefixName || []) as StoreValue[];
95+
return values || [];
96+
};
97+
/**
98+
* Always get latest value in case user update fields by `form` api.
99+
*/
100+
const operations: ListOperations = {
101+
add: (defaultValue, index?: number) => {
102+
// Mapping keys
103+
const newValue = getNewValue();
104+
105+
if (index >= 0 && index <= newValue.length) {
106+
keyManager.keys = [
107+
...keyManager.keys.slice(0, index),
108+
keyManager.id,
109+
...keyManager.keys.slice(index),
110+
];
111+
onChange([...newValue.slice(0, index), defaultValue, ...newValue.slice(index)]);
112+
} else {
113+
if (
114+
process.env.NODE_ENV !== 'production' &&
115+
(index < 0 || index > newValue.length)
116+
) {
117+
warning(
118+
false,
119+
'The second parameter of the add function should be a valid positive number.',
120+
);
121+
}
122+
keyManager.keys = [...keyManager.keys, keyManager.id];
123+
onChange([...newValue, defaultValue]);
102124
}
103-
keyManager.keys = [...keyManager.keys, keyManager.id];
104-
onChange([...newValue, defaultValue]);
105-
}
106-
keyManager.id += 1;
107-
},
108-
remove: (index: number | number[]) => {
109-
const newValue = getNewValue();
110-
const indexSet = new Set(Array.isArray(index) ? index : [index]);
111-
112-
if (indexSet.size <= 0) {
113-
return;
114-
}
115-
keyManager.keys = keyManager.keys.filter((_, keysIndex) => !indexSet.has(keysIndex));
116-
117-
// Trigger store change
118-
onChange(newValue.filter((_, valueIndex) => !indexSet.has(valueIndex)));
119-
},
120-
move(from: number, to: number) {
121-
if (from === to) {
122-
return;
123-
}
124-
const newValue = getNewValue();
125+
keyManager.id += 1;
126+
},
127+
remove: (index: number | number[]) => {
128+
const newValue = getNewValue();
129+
const indexSet = new Set(Array.isArray(index) ? index : [index]);
125130

126-
// Do not handle out of range
127-
if (from < 0 || from >= newValue.length || to < 0 || to >= newValue.length) {
128-
return;
129-
}
131+
if (indexSet.size <= 0) {
132+
return;
133+
}
134+
keyManager.keys = keyManager.keys.filter(
135+
(_, keysIndex) => !indexSet.has(keysIndex),
136+
);
130137

131-
keyManager.keys = move(keyManager.keys, from, to);
138+
// Trigger store change
139+
onChange(newValue.filter((_, valueIndex) => !indexSet.has(valueIndex)));
140+
},
141+
move(from: number, to: number) {
142+
if (from === to) {
143+
return;
144+
}
145+
const newValue = getNewValue();
146+
147+
// Do not handle out of range
148+
if (from < 0 || from >= newValue.length || to < 0 || to >= newValue.length) {
149+
return;
150+
}
132151

133-
// Trigger store change
134-
onChange(move(newValue, from, to));
135-
},
136-
};
152+
keyManager.keys = move(keyManager.keys, from, to);
137153

138-
let listValue = value || [];
139-
if (!Array.isArray(listValue)) {
140-
listValue = [];
154+
// Trigger store change
155+
onChange(move(newValue, from, to));
156+
},
157+
};
158+
159+
let listValue = value || [];
160+
if (!Array.isArray(listValue)) {
161+
listValue = [];
141162

142-
if (process.env.NODE_ENV !== 'production') {
143-
warning(false, `Current value of '${prefixName.join(' > ')}' is not an array type.`);
163+
if (process.env.NODE_ENV !== 'production') {
164+
warning(
165+
false,
166+
`Current value of '${prefixName.join(' > ')}' is not an array type.`,
167+
);
168+
}
144169
}
145-
}
146170

147-
return children(
148-
(listValue as StoreValue[]).map(
149-
(__, index): ListField => {
171+
return children(
172+
(listValue as StoreValue[]).map((__, index): ListField => {
150173
let key = keyManager.keys[index];
151174
if (key === undefined) {
152175
keyManager.keys[index] = keyManager.id;
@@ -159,14 +182,14 @@ const List: React.FunctionComponent<ListProps> = ({
159182
key,
160183
isListField: true,
161184
};
162-
},
163-
),
164-
operations,
165-
meta,
166-
);
167-
}}
168-
</Field>
169-
</FieldContext.Provider>
185+
}),
186+
operations,
187+
meta,
188+
);
189+
}}
190+
</Field>
191+
</FieldContext.Provider>
192+
</ListContext.Provider>
170193
);
171194
};
172195

src/ListContext.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as React from 'react';
2+
import type { InternalNamePath } from './interface';
3+
4+
export interface ListContextProps {
5+
getKey: (namePath: InternalNamePath) => React.Key;
6+
}
7+
8+
const ListContext = React.createContext<ListContextProps | null>(null);
9+
10+
export default ListContext;

tests/list.test.tsx

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import React from 'react';
22
import { act } from 'react-dom/test-utils';
3-
import { mount, ReactWrapper } from 'enzyme';
3+
import { mount } from 'enzyme';
4+
import type { ReactWrapper } from 'enzyme';
45
import { resetWarned } from 'rc-util/lib/warning';
5-
import Form, { Field, List, FormProps } from '../src';
6-
import { ListField, ListOperations, ListProps } from '../src/List';
7-
import { FormInstance, Meta } from '../src/interface';
6+
import Form, { Field, List } from '../src';
7+
import type { FormProps } from '../src';
8+
import type { ListField, ListOperations, ListProps } from '../src/List';
9+
import type { FormInstance, Meta } from '../src/interface';
10+
import ListContext from '../src/ListContext';
811
import { Input } from './common/InfoField';
912
import { changeValue, getField } from './common';
1013
import timeout from './common/timeout';
@@ -58,12 +61,7 @@ describe('Form.List', () => {
5861
);
5962

6063
function matchKey(index, key) {
61-
expect(
62-
getList()
63-
.find(Field)
64-
.at(index)
65-
.key(),
66-
).toEqual(key);
64+
expect(getList().find(Field).at(index).key()).toEqual(key);
6765
}
6866

6967
matchKey(0, '0');
@@ -117,12 +115,7 @@ describe('Form.List', () => {
117115
});
118116

119117
function matchKey(index, key) {
120-
expect(
121-
getList()
122-
.find(Field)
123-
.at(index)
124-
.key(),
125-
).toEqual(key);
118+
expect(getList().find(Field).at(index).key()).toEqual(key);
126119
}
127120

128121
// Add
@@ -267,12 +260,7 @@ describe('Form.List', () => {
267260
});
268261

269262
function matchKey(index, key) {
270-
expect(
271-
getList()
272-
.find(Field)
273-
.at(index)
274-
.key(),
275-
).toEqual(key);
263+
expect(getList().find(Field).at(index).key()).toEqual(key);
276264
}
277265

278266
act(() => {
@@ -533,7 +521,7 @@ describe('Form.List', () => {
533521
it('warning if children is not function', () => {
534522
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
535523

536-
generateForm(<div /> as any);
524+
generateForm((<div />) as any);
537525

538526
expect(errorSpy).toHaveBeenCalledWith('Warning: Form.List only accepts function as children.');
539527

@@ -759,4 +747,36 @@ describe('Form.List', () => {
759747
list: ['light', 'bamboo'],
760748
});
761749
});
750+
751+
it('ListContext', () => {
752+
const Hooker = ({ field }: any) => {
753+
const { getKey } = React.useContext(ListContext);
754+
755+
return (
756+
<>
757+
<span className="internal-name">{getKey(['list', field.name])}</span>
758+
<Field {...field}>
759+
<Input />
760+
</Field>
761+
</>
762+
);
763+
};
764+
765+
const [wrapper] = generateForm(
766+
fields => (
767+
<div>
768+
{fields.map(field => {
769+
return <Hooker field={field} key={field.key} />;
770+
})}
771+
</div>
772+
),
773+
{
774+
initialValues: {
775+
list: [''],
776+
},
777+
},
778+
);
779+
780+
expect(wrapper.find('.internal-name').text()).toEqual('0');
781+
});
762782
});

0 commit comments

Comments
 (0)