Skip to content

Commit 187658d

Browse files
isFocused, isTouched.
1 parent 3de9bde commit 187658d

File tree

3 files changed

+53
-8
lines changed

3 files changed

+53
-8
lines changed

src/components/reset.tsx

Whitespace-only changes.

src/components/text-field.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,6 @@ function useField(props: TextFieldProps): FieldResult {
3434

3535
const fieldId = store.generateFieldId(props.name, groupId);
3636

37-
const onChange: React.ChangeEventHandler<HTMLInputElement> = event => {
38-
store.updateValue(fieldId, event.target.value);
39-
};
40-
4137
useEffect(() => {
4238
if (defaultValue == null) {
4339
defaultValue = "";
@@ -54,6 +50,9 @@ function useField(props: TextFieldProps): FieldResult {
5450
if (field == null) {
5551
return;
5652
}
53+
54+
// Initial update is skipped, because field is registered during first render and
55+
// store listener is added asynchronously. The change action is emitted in-between.
5756
setCurrentValue(field.currentValue);
5857
};
5958
storeUpdated();
@@ -66,13 +65,22 @@ function useField(props: TextFieldProps): FieldResult {
6665
// Then, unregister field.
6766
store.unregisterField(fieldId);
6867
};
69-
}, []);
68+
}, [props.name, groupId, defaultValue, initialValue]);
69+
7070
return {
7171
fieldId: fieldId,
7272
store: store,
7373
fieldProps: {
7474
value: currentValue,
75-
onChange: onChange
75+
onChange: event => store.updateValue(fieldId, event.target.value),
76+
onFocus: () => {
77+
console.log(`Field '${fieldId}' has been focused.`);
78+
store.focus(fieldId);
79+
},
80+
onBlur: () => {
81+
console.log(`Field '${fieldId}' has been blurred.`);
82+
store.blur(fieldId);
83+
}
7684
}
7785
};
7886
}

src/stores/group-store.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import { TinyEmitter, Callback } from "../helpers/emitter";
2-
import produce from "immer";
2+
import { produce } from "immer";
33

44
export const SEPARATOR = ".";
55

66
interface Field {
77
id: string;
88
name: string;
9+
910
defaultValue?: string;
1011
initialValue?: string;
1112
currentValue: string;
13+
14+
isFocused: boolean;
15+
isTouched: boolean;
1216
}
1317

1418
type Fields = {
@@ -47,7 +51,9 @@ export class GroupStoreMutable extends TinyEmitter<Callback> {
4751
name: name,
4852
defaultValue: defaultValue,
4953
initialValue: initialValue,
50-
currentValue: initialValue || defaultValue
54+
currentValue: initialValue || defaultValue,
55+
isFocused: false,
56+
isTouched: false
5157
};
5258

5359
this.fields = produce(this.fields, fields => {
@@ -84,6 +90,37 @@ export class GroupStoreMutable extends TinyEmitter<Callback> {
8490
this.emit();
8591
}
8692

93+
public focus(fieldId: string): void {
94+
this.setFocused(fieldId, true);
95+
this.emit();
96+
}
97+
98+
public blur(fieldId: string): void {
99+
this.setFocused(fieldId, false);
100+
this.emit();
101+
}
102+
103+
private setFocused(fieldId: string, isFocused: boolean): void {
104+
console.log(`Setting focus for field '${fieldId}' to ${isFocused}`);
105+
if (this.fields[fieldId] == null) {
106+
throw new Error(
107+
`Cannot update non-existent field value. (field id '${fieldId}').`
108+
);
109+
}
110+
this.fields = produce(this.fields, fields => {
111+
const field = fields[fieldId];
112+
if (field == null) {
113+
return;
114+
}
115+
field.isFocused = isFocused;
116+
117+
// If the field is not touched yet and got focused, make it touched.
118+
if (!field.isTouched && isFocused) {
119+
field.isTouched = true;
120+
}
121+
});
122+
}
123+
87124
public toObject(): unknown {
88125
const result: { [key: string]: unknown } = {};
89126
for (const key in this.fields) {

0 commit comments

Comments
 (0)