Skip to content

Commit cbf3ddb

Browse files
Reset implemented. Group state unified into a single object.
1 parent 8c5edbc commit cbf3ddb

File tree

4 files changed

+128
-38
lines changed

4 files changed

+128
-38
lines changed

src/components/reset.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React, { useContext } from "react";
2+
import { GroupContext } from "../contexts/group-context";
3+
4+
export interface ResetButtonProps {
5+
children: React.ReactNode;
6+
}
7+
8+
export const ResetButton = (props: ResetButtonProps) => {
9+
const { store, groupId, test } = useContext(GroupContext);
10+
11+
const onClick: React.MouseEventHandler<HTMLButtonElement> = event => {
12+
store.reset();
13+
};
14+
15+
return <button onClick={onClick}>{props.children}</button>;
16+
};

src/components/text-field.tsx

-4
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ function useField(props: TextFieldProps): FieldResult {
3939
defaultValue = "";
4040
}
4141

42-
if (initialValue == null) {
43-
initialValue = defaultValue;
44-
}
45-
4642
store.registerField(props.name, groupId, defaultValue, initialValue);
4743

4844
const storeUpdated = () => {

src/stores/group-store.ts

+106-34
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,71 @@ import { produce } from "immer";
33

44
export const SEPARATOR = ".";
55

6+
interface Status {
7+
isFocused: boolean;
8+
isTouched: boolean;
9+
}
10+
611
interface Field {
712
id: string;
813
name: string;
914

10-
defaultValue?: string;
11-
initialValue?: string;
15+
defaultValue: string;
16+
initialValue: string;
1217
currentValue: string;
1318

14-
isFocused: boolean;
15-
isTouched: boolean;
19+
status: Status;
1620
}
1721

1822
type Fields = {
1923
[key: string]: Field | undefined;
2024
};
2125

22-
export class GroupStoreMutable extends TinyEmitter<Callback> {
23-
protected fields: Fields = {};
26+
interface GroupState {
27+
fields: Fields;
28+
status: Status;
29+
}
30+
31+
const defaultStatus = (): Status => ({ isFocused: false, isTouched: false });
2432

25-
constructor() {
26-
super();
33+
const calculateGroupStatus = (state: GroupState): Status => {
34+
let isFocused = false;
35+
let isTouched = false;
36+
37+
for (const key of Object.keys(state.fields)) {
38+
const field = state.fields[key];
39+
if (field == null) {
40+
continue;
41+
}
42+
43+
if (field.status.isFocused) {
44+
isFocused = true;
45+
}
46+
if (field.status.isTouched) {
47+
isTouched = true;
48+
}
49+
50+
// If group is both focused and touched already, nothing is going to change anymore
51+
if (isFocused && isTouched) {
52+
// Thus, break.
53+
break;
54+
}
2755
}
2856

57+
return {
58+
isFocused: isFocused,
59+
isTouched: isTouched
60+
};
61+
};
62+
63+
export class GroupStoreMutable extends TinyEmitter<Callback> {
64+
protected state: GroupState = {
65+
fields: {},
66+
status: defaultStatus()
67+
};
68+
2969
public getField(fieldId: string): Field | undefined {
30-
return this.fields[fieldId];
70+
return this.state.fields[fieldId];
3171
}
3272

3373
public generateFieldId(name: string, groupId: string): string {
@@ -38,56 +78,60 @@ export class GroupStoreMutable extends TinyEmitter<Callback> {
3878
name: string,
3979
groupId: string,
4080
defaultValue: string,
41-
initialValue: string
81+
initialValue?: string
4282
): void {
4383
const id = this.generateFieldId(name, groupId);
4484

45-
if (this.fields[id] != null) {
85+
if (this.state.fields[id] != null) {
4686
throw new Error(`Field with an id '${id}' is already registered.`);
4787
}
4888

89+
if (initialValue == null) {
90+
initialValue = defaultValue;
91+
}
92+
4993
const newField: Field = {
5094
id: id,
5195
name: name,
5296
defaultValue: defaultValue,
5397
initialValue: initialValue,
54-
currentValue: initialValue || defaultValue,
55-
isFocused: false,
56-
isTouched: false
98+
currentValue: initialValue,
99+
status: defaultStatus()
57100
};
58101

59-
this.fields = produce(this.fields, fields => {
60-
fields[id] = newField;
102+
this.state = produce(this.state, state => {
103+
state.fields[id] = newField;
61104
});
62105
this.emit();
63106
}
64107

65108
public unregisterField(id: string): void {
66-
if (this.fields[id] == null) {
109+
if (this.state.fields[id] == null) {
67110
return;
68111
}
69112

70-
this.fields = produce(this.fields, fields => {
71-
fields[id] = undefined;
113+
this.state = produce(this.state, state => {
114+
state.fields[id] = undefined;
72115
});
73116
this.emit();
74117
}
75118

76119
public updateValue(fieldId: string, value: string): void {
77-
if (this.fields[fieldId] == null) {
120+
console.log(`Value updated ${fieldId}`);
121+
if (this.state.fields[fieldId] == null) {
78122
throw new Error(
79123
`Cannot update non-existent field value. (field id '${fieldId}').`
80124
);
81125
}
82-
this.fields = produce(this.fields, fields => {
83-
const field = fields[fieldId];
126+
this.state = produce(this.state, state => {
127+
const field = state.fields[fieldId];
84128
if (field == null) {
85129
return;
86130
}
87131

88132
// Value equality check should be abstracted for particular component and value type
89133
if (field.currentValue !== value) {
90-
field.isTouched = true;
134+
field.status.isTouched = true;
91135
}
92136
field.currentValue = value;
93137
});
@@ -96,41 +140,69 @@ export class GroupStoreMutable extends TinyEmitter<Callback> {
96140
}
97141

98142
public focus(fieldId: string): void {
143+
console.log(`Focus ${fieldId}`);
99144
this.setFocused(fieldId, true);
100145
this.emit();
101146
}
102147

103148
public blur(fieldId: string): void {
149+
console.log(`Blur ${fieldId}`);
104150
this.setFocused(fieldId, false);
105151
this.emit();
106152
}
107153

108154
private setFocused(fieldId: string, isFocused: boolean): void {
109-
console.log(`Setting focus for field '${fieldId}' to ${isFocused}`);
110-
if (this.fields[fieldId] == null) {
155+
if (this.state.fields[fieldId] == null) {
111156
throw new Error(
112157
`Cannot update non-existent field value. (field id '${fieldId}').`
113158
);
114159
}
115-
this.fields = produce(this.fields, fields => {
116-
const field = fields[fieldId];
117-
if (field == null) {
160+
161+
this.state = produce(this.state, state => {
162+
const field = state.fields[fieldId];
163+
if (field == null || field.status.isFocused === isFocused) {
118164
return;
119165
}
120-
field.isFocused = isFocused;
166+
167+
field.status.isFocused = isFocused;
121168

122169
// If the field is not touched yet and got focused, make it touched.
123-
if (!field.isTouched && isFocused) {
124-
field.isTouched = true;
170+
if (!field.status.isTouched && isFocused) {
171+
field.status.isTouched = true;
172+
}
173+
174+
// Recalculate group status, because field status has changed
175+
state.status = calculateGroupStatus(state);
176+
});
177+
}
178+
179+
public reset(): void {
180+
console.log(`Reset state`);
181+
this.state = produce(this.state, state => {
182+
// Reset fields
183+
const fields = state.fields;
184+
for (const key of Object.keys(fields)) {
185+
const field = fields[key];
186+
if (field == null) {
187+
continue;
188+
}
189+
190+
field.currentValue = field.initialValue;
191+
field.status = defaultStatus();
125192
}
193+
194+
// Reset group status
195+
state.status = defaultStatus();
126196
});
197+
this.emit();
127198
}
128199

129200
public toObject(): unknown {
130201
const result: { [key: string]: unknown } = {};
131-
for (const key in this.fields) {
132-
if (this.fields.hasOwnProperty(key)) {
133-
const field = this.fields[key];
202+
const fields = this.state.fields;
203+
for (const key in fields) {
204+
if (fields.hasOwnProperty(key)) {
205+
const field = fields[key];
134206
if (field == null) {
135207
continue;
136208
}

src/tests/test1.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useState } from "react";
22
import { TextField } from "../components/text-field";
3+
import { ResetButton } from "../components/reset";
34

45
export const Test1 = () => {
56
const [show, setShow] = useState(true);
@@ -19,6 +20,11 @@ export const Test1 = () => {
1920
<TextField name="lastName" initialValue="Smith" />
2021
) : null}
2122
</label>
23+
<div>
24+
<ResetButton>
25+
Reset
26+
</ResetButton>
27+
</div>
2228
</div>
2329
</>
2430
);

0 commit comments

Comments
 (0)