mayoiga 0.1.2 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -35
- package/dist/component.d.ts +1 -1
- package/dist/component.test.js +62 -3
- package/dist/index.d.ts +19 -4
- package/dist/index.js +11 -7
- package/dist/index.test.js +5 -2
- package/docs/.nojekyll +1 -0
- package/docs/assets/highlight.css +92 -0
- package/docs/assets/icons.css +1043 -0
- package/docs/assets/{images/icons.png → icons.png} +0 -0
- package/docs/assets/{images/icons@2x.png → icons@2x.png} +0 -0
- package/docs/assets/main.js +52 -0
- package/docs/assets/search.js +1 -0
- package/docs/assets/style.css +1414 -0
- package/docs/assets/{images/widgets.png → widgets.png} +0 -0
- package/docs/assets/{images/widgets@2x.png → widgets@2x.png} +0 -0
- package/docs/index.html +40 -248
- package/docs/modules.html +4 -0
- package/package.json +21 -19
- package/src/component.test.tsx +127 -14
- package/src/component.tsx +9 -5
- package/src/index.test.tsx +19 -17
- package/src/index.ts +64 -35
- package/yarn.lock +537 -550
- package/docs/assets/css/main.css +0 -2328
- package/docs/assets/js/main.js +0 -1
- package/docs/assets/js/search.js +0 -3
- package/docs/globals.html +0 -185
- package/docs/modules/_actions_.html +0 -398
- package/docs/modules/_forms_.html +0 -362
- package/docs/modules/_index_.html +0 -170
- package/docs/modules/_inputprotocol_.html +0 -319
- package/docs/modules/_mayoiga_.html +0 -783
- package/docs/modules/_reducer_.html +0 -417
package/src/component.test.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import {render, screen, waitFor} from '@testing-library/react';
|
|
3
2
|
import userEvent from '@testing-library/user-event';
|
|
4
3
|
import React from 'react';
|
|
@@ -7,14 +6,19 @@ import {test, expect} from 'vitest';
|
|
|
7
6
|
|
|
8
7
|
import * as zod from 'zod';
|
|
9
8
|
|
|
10
|
-
import {
|
|
9
|
+
import {Field, Slicer} from './component';
|
|
10
|
+
|
|
11
|
+
import {createFormHook} from './index';
|
|
11
12
|
|
|
12
|
-
import {
|
|
13
|
+
import type {Controller} from './index';
|
|
13
14
|
|
|
14
15
|
const testSchema = zod.object({
|
|
15
16
|
name: zod.string(),
|
|
16
17
|
description: zod.string(),
|
|
17
|
-
age: zod
|
|
18
|
+
age: zod
|
|
19
|
+
.string()
|
|
20
|
+
.transform((val) => Number(val))
|
|
21
|
+
.refine((val) => (Number.isNaN(val) ? 0 : val)),
|
|
18
22
|
marked: zod.boolean(),
|
|
19
23
|
});
|
|
20
24
|
|
|
@@ -24,9 +28,9 @@ type FormStateBeforeValidation = {
|
|
|
24
28
|
description: string;
|
|
25
29
|
age: string;
|
|
26
30
|
marked: boolean;
|
|
27
|
-
}
|
|
31
|
+
};
|
|
28
32
|
|
|
29
|
-
const createTestHook =
|
|
33
|
+
const createTestHook = () => createFormHook({name: '', description: '', age: '', marked: false} as FormStateBeforeValidation, testSchema);
|
|
30
34
|
|
|
31
35
|
// afterEach(cleanup);
|
|
32
36
|
|
|
@@ -36,11 +40,11 @@ test('Field component works fine in basic usage.', async () => {
|
|
|
36
40
|
const Top = () => {
|
|
37
41
|
return (
|
|
38
42
|
<div>
|
|
39
|
-
<Field controller={TestFormHook.controller} name="age">
|
|
40
|
-
(tool) => <input data-testid="age-input" {...tool} />
|
|
41
|
-
|
|
43
|
+
<Field controller={TestFormHook.controller} name="age">
|
|
44
|
+
{(tool) => <input data-testid="age-input" {...tool} />}
|
|
45
|
+
</Field>
|
|
42
46
|
</div>
|
|
43
|
-
)
|
|
47
|
+
);
|
|
44
48
|
};
|
|
45
49
|
|
|
46
50
|
render(<Top />);
|
|
@@ -66,11 +70,13 @@ test('Slice component works fine in basic usage.', async () => {
|
|
|
66
70
|
const Top = () => {
|
|
67
71
|
return (
|
|
68
72
|
<div>
|
|
69
|
-
<Slicer controller={TestFormHook.controller} selector={s => [s.value.age] as const}>
|
|
70
|
-
(tool, age: string) =>
|
|
71
|
-
|
|
73
|
+
<Slicer controller={TestFormHook.controller} selector={(s) => [s.value.age] as const}>
|
|
74
|
+
{(tool, age: string) => (
|
|
75
|
+
<input data-testid="age-input" name="age" value={age} onChange={(e) => tool.handleChange('age' as const, e.target.value)} />
|
|
76
|
+
)}
|
|
77
|
+
</Slicer>
|
|
72
78
|
</div>
|
|
73
|
-
)
|
|
79
|
+
);
|
|
74
80
|
};
|
|
75
81
|
|
|
76
82
|
render(<Top />);
|
|
@@ -89,3 +95,110 @@ test('Slice component works fine in basic usage.', async () => {
|
|
|
89
95
|
});
|
|
90
96
|
});
|
|
91
97
|
});
|
|
98
|
+
|
|
99
|
+
test('A user able to create a reusable component', async () => {
|
|
100
|
+
const TestFormHook = createTestHook();
|
|
101
|
+
|
|
102
|
+
function NarrowTypedExpected(props: {controller: Controller<{age: string}>}) {
|
|
103
|
+
return (
|
|
104
|
+
<Field controller={props.controller} name="age">
|
|
105
|
+
{(tool) => <input data-testid="age-input" {...tool} />}
|
|
106
|
+
</Field>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function ErrorDisplayer(props: {controller: Controller<{age: string}>}) {
|
|
111
|
+
const {controller} = props;
|
|
112
|
+
return (
|
|
113
|
+
<Slicer controller={controller} selector={(s) => [s.value.age] as const}>
|
|
114
|
+
{(tool, age: string) => (
|
|
115
|
+
<button
|
|
116
|
+
onClick={(e) => {
|
|
117
|
+
controller.actions.pushFormErrors((_state) => ({age: age === '42' ? 'yes!!!' : 'noooo'}));
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
)}
|
|
121
|
+
</Slicer>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
render(
|
|
126
|
+
<div>
|
|
127
|
+
<NarrowTypedExpected controller={TestFormHook.controller} />
|
|
128
|
+
<ErrorDisplayer controller={TestFormHook.controller} />
|
|
129
|
+
</div>,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const INPUT_AGE = '42';
|
|
133
|
+
|
|
134
|
+
// calling `userEvent` in here will cause race condition...
|
|
135
|
+
await userEvent.type(screen.getByTestId('age-input'), INPUT_AGE);
|
|
136
|
+
await userEvent.click(screen.getByRole('button'));
|
|
137
|
+
|
|
138
|
+
await waitFor(() => {
|
|
139
|
+
const ageInput: HTMLInputElement = screen.getByTestId('age-input');
|
|
140
|
+
expect(ageInput.value).toBe(INPUT_AGE);
|
|
141
|
+
TestFormHook.api.actions.peek((s) => {
|
|
142
|
+
expect(s.value.age).toBe(INPUT_AGE);
|
|
143
|
+
expect(s.errors.age).toBe('yes!!!');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('`deps` field could destruct memoization', async () => {
|
|
149
|
+
const TestFormHook = createTestHook();
|
|
150
|
+
// intentional sample to replicate the situation
|
|
151
|
+
const OtherFormHook = createFormHook({minAge: '30'}, zod.object({minAge: zod.string()}));
|
|
152
|
+
|
|
153
|
+
const DATA_ID_RE_RENDER_BY_DEPS = 'refresh-by-deps';
|
|
154
|
+
const DATA_ID_NOT_RERENDER_NO_DEPS = 'no-refersh-no-deps';
|
|
155
|
+
|
|
156
|
+
function App() {
|
|
157
|
+
const minAge = OtherFormHook.useSelector((s) => Number(s.value.minAge));
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<div>
|
|
161
|
+
<Field controller={OtherFormHook.controller} name="minAge">
|
|
162
|
+
{(tool) => <input data-testid="minAgeInput" type="number" {...tool} />}
|
|
163
|
+
</Field>
|
|
164
|
+
<Field controller={TestFormHook.controller} name="age">
|
|
165
|
+
{(tool) => <input data-testid="ageInput" type="number" {...tool} />}
|
|
166
|
+
</Field>
|
|
167
|
+
<Slicer controller={TestFormHook.controller} selector={(s) => [Number(s.value.age)]} deps={[minAge]}>
|
|
168
|
+
{(_tool, age: number) => (
|
|
169
|
+
<div data-testid={DATA_ID_RE_RENDER_BY_DEPS}>{age >= minAge ? 'You can buy this content' : 'go home'}</div>
|
|
170
|
+
)}
|
|
171
|
+
</Slicer>
|
|
172
|
+
<Slicer controller={TestFormHook.controller} selector={(s) => [Number(s.value.age)]}>
|
|
173
|
+
{(_tool, age: number) => (
|
|
174
|
+
<div data-testid={DATA_ID_NOT_RERENDER_NO_DEPS}>{age >= minAge ? 'You can buy this content' : 'go home'}</div>
|
|
175
|
+
)}
|
|
176
|
+
</Slicer>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
render(<App />);
|
|
182
|
+
|
|
183
|
+
await userEvent.type(screen.getByTestId('ageInput'), '16');
|
|
184
|
+
|
|
185
|
+
await waitFor(() => {
|
|
186
|
+
expect(screen.getByTestId(DATA_ID_RE_RENDER_BY_DEPS).textContent).toBe('go home');
|
|
187
|
+
// expect(await screen.findByText('You can buy this content')).toBeDefined();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await userEvent.clear(screen.getByTestId('minAgeInput'));
|
|
191
|
+
await userEvent.type(screen.getByTestId('minAgeInput'), '10');
|
|
192
|
+
|
|
193
|
+
await waitFor(() => {
|
|
194
|
+
expect(screen.getByTestId(DATA_ID_RE_RENDER_BY_DEPS).textContent, 'Slicer should re-execute renderProps if `deps` are changed').toBe(
|
|
195
|
+
'You can buy this content',
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
expect(
|
|
200
|
+
screen.getByTestId(DATA_ID_NOT_RERENDER_NO_DEPS).textContent,
|
|
201
|
+
'Same logic but would not refresh because external dependencies are not applied',
|
|
202
|
+
).toBe('go home');
|
|
203
|
+
});
|
|
204
|
+
});
|
package/src/component.tsx
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import type {SliceProps, FieldProps, StateRestriction, Controller
|
|
3
|
+
import type {SliceProps, FieldProps, StateRestriction, Controller} from './index';
|
|
4
4
|
|
|
5
|
-
type ControlledComponentProps<State, Props> = Props & {
|
|
5
|
+
type ControlledComponentProps<State extends StateRestriction, Props> = Props & {
|
|
6
6
|
controller: Controller<State>;
|
|
7
|
-
}
|
|
7
|
+
};
|
|
8
8
|
|
|
9
|
-
export function Slicer<State extends StateRestriction, R extends ReadonlyArray<unknown>>(
|
|
9
|
+
export function Slicer<State extends StateRestriction, R extends ReadonlyArray<unknown>>(
|
|
10
|
+
props: ControlledComponentProps<State, SliceProps<State, R>>,
|
|
11
|
+
) {
|
|
10
12
|
const {controller, ...restProps} = props;
|
|
11
13
|
const {Slicer: Delegate} = controller.components;
|
|
12
14
|
return <Delegate {...restProps} />;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
export function Field<State extends StateRestriction, Name extends keyof State>(
|
|
17
|
+
export function Field<State extends StateRestriction, Name extends keyof State>(
|
|
18
|
+
props: ControlledComponentProps<State, FieldProps<State, Name>>,
|
|
19
|
+
) {
|
|
16
20
|
const {controller, ...restProps} = props;
|
|
17
21
|
const {Field: Delegate} = controller.components;
|
|
18
22
|
return <Delegate {...restProps} />;
|
package/src/index.test.tsx
CHANGED
|
@@ -7,14 +7,16 @@ import {test, expect} from 'vitest';
|
|
|
7
7
|
import * as zod from 'zod';
|
|
8
8
|
|
|
9
9
|
import {createFormHook, useFormSlice} from './index';
|
|
10
|
-
|
|
11
|
-
import type {Controller} from './index';
|
|
12
10
|
|
|
11
|
+
import type {Controller} from './index';
|
|
13
12
|
|
|
14
13
|
const testSchema = zod.object({
|
|
15
14
|
name: zod.string(),
|
|
16
15
|
description: zod.string(),
|
|
17
|
-
age: zod
|
|
16
|
+
age: zod
|
|
17
|
+
.string()
|
|
18
|
+
.transform((val) => Number(val))
|
|
19
|
+
.refine((val) => (Number.isNaN(val) ? 0 : val)),
|
|
18
20
|
marked: zod.boolean(),
|
|
19
21
|
});
|
|
20
22
|
|
|
@@ -24,13 +26,13 @@ type FormStateBeforeValidation = {
|
|
|
24
26
|
description: string;
|
|
25
27
|
age: string;
|
|
26
28
|
marked: boolean;
|
|
27
|
-
}
|
|
29
|
+
};
|
|
28
30
|
|
|
29
|
-
const createTestHook =
|
|
31
|
+
const createTestHook = () => createFormHook({name: '', description: '', age: '', marked: false} as FormStateBeforeValidation, testSchema);
|
|
30
32
|
|
|
31
33
|
const AgeInputComponent = (props: {controller: Controller<FormStateBeforeValidation>}) => {
|
|
32
34
|
const {controller} = props;
|
|
33
|
-
const [[age, errorMessage], {handleChange}] = useFormSlice(controller, s => [s.value.age, s.errors.age] as const);
|
|
35
|
+
const [[age, errorMessage], {handleChange}] = useFormSlice(controller, (s) => [s.value.age, s.errors.age] as const);
|
|
34
36
|
return (
|
|
35
37
|
<div>
|
|
36
38
|
<input
|
|
@@ -56,7 +58,7 @@ test('useSelector can select the fields', async () => {
|
|
|
56
58
|
<div>
|
|
57
59
|
<AgeInputComponent controller={TestFormHook.controller} />
|
|
58
60
|
</div>
|
|
59
|
-
)
|
|
61
|
+
);
|
|
60
62
|
};
|
|
61
63
|
|
|
62
64
|
render(<Top />);
|
|
@@ -81,12 +83,12 @@ test('zod parse value before submit', async () => {
|
|
|
81
83
|
const PARSED_RESULT = Number(INPUT_AGE);
|
|
82
84
|
|
|
83
85
|
const handleSubmitTester = TestFormHook.handleSubmit((e) => (result) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
if (result.success) {
|
|
87
|
+
expect(result.data.age).toBe(PARSED_RESULT);
|
|
88
|
+
} else {
|
|
89
|
+
expect('should not called').toBe(result.error);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
90
92
|
|
|
91
93
|
const Top = () => {
|
|
92
94
|
return (
|
|
@@ -94,16 +96,16 @@ test('zod parse value before submit', async () => {
|
|
|
94
96
|
<AgeInputComponent controller={TestFormHook} />
|
|
95
97
|
<button data-testid="submit-button" onClick={handleSubmitTester} />
|
|
96
98
|
</div>
|
|
97
|
-
)
|
|
99
|
+
);
|
|
98
100
|
};
|
|
99
101
|
|
|
100
102
|
render(<Top />);
|
|
101
103
|
|
|
102
|
-
const ageInput: HTMLInputElement = screen.getByTestId('age-input')
|
|
104
|
+
const ageInput: HTMLInputElement = screen.getByTestId('age-input');
|
|
103
105
|
await userEvent.type(ageInput, INPUT_AGE);
|
|
104
106
|
|
|
105
107
|
await waitFor(() => {
|
|
106
|
-
const ageInput: HTMLInputElement = screen.getByTestId('age-input')
|
|
108
|
+
const ageInput: HTMLInputElement = screen.getByTestId('age-input');
|
|
107
109
|
expect(ageInput.value).toBe(INPUT_AGE);
|
|
108
110
|
});
|
|
109
111
|
|
|
@@ -117,4 +119,4 @@ test('zod parse value before submit', async () => {
|
|
|
117
119
|
expect(s.errors.age).toBeNull();
|
|
118
120
|
});
|
|
119
121
|
});
|
|
120
|
-
});
|
|
122
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -35,14 +35,29 @@ export type FieldProps<State extends StateRestriction, Name extends keyof State>
|
|
|
35
35
|
value: State[Name],
|
|
36
36
|
errorMessage: string | null,
|
|
37
37
|
) => ReactNode;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Mayoiga Field will memoize the result of renderProps for performance.
|
|
41
|
+
* If your renderProps depends on external variables, you should apply those on deps
|
|
42
|
+
*/
|
|
43
|
+
deps?: ReadonlyArray<unknown>;
|
|
38
44
|
};
|
|
39
45
|
|
|
40
46
|
export type SliceProps<State extends StateRestriction, Selected extends ReadonlyArray<unknown>> = {
|
|
41
47
|
selector: (s: FullFormState<State>) => Selected;
|
|
42
48
|
children: (tool: {handleChange: HandleChangeAction<State>}, ...value: Readonly<Selected>) => ReactNode;
|
|
49
|
+
/**
|
|
50
|
+
* Mayoiga Slicer will memoize the result of renderProps for performance.
|
|
51
|
+
* If your renderProps depends on external variables, you should apply those on deps
|
|
52
|
+
*
|
|
53
|
+
* ```typescript
|
|
54
|
+
* <Slicer selector={s => [s.value.selected]} deps={[selection]}>{(tools, selected: string) => <AwesomeSelection value={selected} options={selection} />}</Slicer>
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
deps?: ReadonlyArray<unknown>;
|
|
43
58
|
};
|
|
44
59
|
|
|
45
|
-
type FormControllerBehavior<StateBeforeValidation> = {
|
|
60
|
+
type FormControllerBehavior<StateBeforeValidation extends StateRestriction> = {
|
|
46
61
|
/** For testing */
|
|
47
62
|
peek: (
|
|
48
63
|
eyeball: (prev: FullFormState<StateBeforeValidation>) => void,
|
|
@@ -65,7 +80,11 @@ type FormControllerBehavior<StateBeforeValidation> = {
|
|
|
65
80
|
/**
|
|
66
81
|
* You can handle server side or other external error via this action.
|
|
67
82
|
*/
|
|
68
|
-
|
|
83
|
+
pushFormErrors: (
|
|
84
|
+
validator: (state: StateBeforeValidation) => Partial<FormErrors<StateBeforeValidation>>,
|
|
85
|
+
) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
86
|
+
|
|
87
|
+
reduceFormErrors: (
|
|
69
88
|
reducer: (prev: FormErrors<StateBeforeValidation>) => FormErrors<StateBeforeValidation>,
|
|
70
89
|
) => (prev: FullFormState<StateBeforeValidation>) => FullFormState<StateBeforeValidation>;
|
|
71
90
|
|
|
@@ -150,7 +169,11 @@ function createFormStore<StateBeforeValidation extends StateRestriction, Schema
|
|
|
150
169
|
|
|
151
170
|
return {...prev, isDirty: true, isValid: false, errors: newErrors};
|
|
152
171
|
},
|
|
153
|
-
|
|
172
|
+
pushFormErrors: (validator) => (prev) => {
|
|
173
|
+
const pushedErrors = validator(prev.value);
|
|
174
|
+
return {...prev, errors: {...prev.errors, ...pushedErrors}};
|
|
175
|
+
},
|
|
176
|
+
reduceFormErrors: (reducer) => (prev) => {
|
|
154
177
|
return {...prev, errors: reducer(prev.errors)};
|
|
155
178
|
},
|
|
156
179
|
handleChange: (name, value) => (prev) => {
|
|
@@ -174,9 +197,9 @@ function createFormStore<StateBeforeValidation extends StateRestriction, Schema
|
|
|
174
197
|
type ActionsCanBePublic<State extends StateRestriction> = {
|
|
175
198
|
reset: VoidFunction;
|
|
176
199
|
initializeForm: (initial: Partial<State>) => void;
|
|
200
|
+
pushFormErrors: (validator: (state: State) => Partial<FormErrors<State>>) => void;
|
|
177
201
|
handleChange: HandleChangeAction<State>;
|
|
178
202
|
handleBulkChange: (setter: (prev: State) => Partial<State>) => void;
|
|
179
|
-
handleError: (reducer: (prev: FormErrors<State>) => FormErrors<State>) => void;
|
|
180
203
|
};
|
|
181
204
|
|
|
182
205
|
/**
|
|
@@ -211,7 +234,7 @@ type FormHook<State extends StateRestriction, Schema extends ZodType<any, any, a
|
|
|
211
234
|
/**
|
|
212
235
|
* @typeParam InitialState should be shallow
|
|
213
236
|
*/
|
|
214
|
-
export function createFormHook<StateBeforeValidation, Schema extends ZodType<any, any, any>>(
|
|
237
|
+
export function createFormHook<StateBeforeValidation extends StateRestriction, Schema extends ZodType<any, any, any>>(
|
|
215
238
|
initialState: StateBeforeValidation,
|
|
216
239
|
schema: Schema,
|
|
217
240
|
): FormHook<StateBeforeValidation, Schema> {
|
|
@@ -248,40 +271,46 @@ export function createFormHook<StateBeforeValidation, Schema extends ZodType<any
|
|
|
248
271
|
|
|
249
272
|
components: {
|
|
250
273
|
Slicer<R extends ReadonlyArray<unknown>>(props: SliceProps<StateBeforeValidation, R>) {
|
|
251
|
-
const {selector, children} = props;
|
|
252
|
-
const
|
|
253
|
-
return useMemo(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
274
|
+
const {selector, children, deps} = props;
|
|
275
|
+
const slicedValues = formHook.useSelector(selector);
|
|
276
|
+
return useMemo(
|
|
277
|
+
() => {
|
|
278
|
+
const renderContent = children({handleChange: formHook.actions.handleChange}, ...slicedValues);
|
|
279
|
+
return createElement(Fragment, {}, renderContent);
|
|
280
|
+
},
|
|
281
|
+
deps ? [...slicedValues, ...deps] : slicedValues,
|
|
282
|
+
);
|
|
257
283
|
},
|
|
258
284
|
Field<Name extends keyof StateBeforeValidation>(props: FieldProps<StateBeforeValidation, Name>) {
|
|
259
|
-
const {name, children} = props;
|
|
285
|
+
const {name, children, deps} = props;
|
|
260
286
|
const [value, errMsg] = store.useSelector((s) => [s.value[name], s.errors[name]] as const);
|
|
261
|
-
return useMemo(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
287
|
+
return useMemo(
|
|
288
|
+
() => {
|
|
289
|
+
const renderContent = children(
|
|
290
|
+
{
|
|
291
|
+
name,
|
|
292
|
+
value,
|
|
293
|
+
onChange: (name_or_event: unknown, value_or_none?: unknown) => {
|
|
294
|
+
if (typeof name_or_event === 'string') {
|
|
295
|
+
// assumes name is valid if it's string
|
|
296
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
297
|
+
return store.actions.handleChange(name_or_event as Name, value_or_none as any);
|
|
298
|
+
}
|
|
299
|
+
if (isChangeEvent(name_or_event)) {
|
|
300
|
+
const target: any = name_or_event.currentTarget || name_or_event.target;
|
|
301
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
|
|
302
|
+
return store.actions.handleChange(target.name, target.value);
|
|
303
|
+
}
|
|
304
|
+
throw new Error('`handleChange` handles unexpected formed object.');
|
|
305
|
+
},
|
|
278
306
|
},
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
307
|
+
value,
|
|
308
|
+
errMsg,
|
|
309
|
+
);
|
|
310
|
+
return createElement(Fragment, {}, renderContent);
|
|
311
|
+
},
|
|
312
|
+
deps ? [value, errMsg, ...deps] : [value, errMsg],
|
|
313
|
+
);
|
|
285
314
|
},
|
|
286
315
|
},
|
|
287
316
|
actions: store.actions,
|