Skip to content

Commit ca9fd0b

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

File tree

4 files changed

+131
-38
lines changed

4 files changed

+131
-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

+109-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 });
32+
33+
const calculateGroupStatus = (state: GroupState): Status => {
34+
let isFocused = false;
35+
let isTouched = false;
2436

25-
constructor() {
26-
super();
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,63 @@ 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: {
100+
isFocused: false,
101+
isTouched: false
102+
}
57103
};
58104

59-
this.fields = produce(this.fields, fields => {
60-
fields[id] = newField;
105+
this.state = produce(this.state, state => {
106+
state.fields[id] = newField;
61107
});
62108
this.emit();
63109
}
64110

65111
public unregisterField(id: string): void {
66-
if (this.fields[id] == null) {
112+
if (this.state.fields[id] == null) {
67113
return;
68114
}
69115

70-
this.fields = produce(this.fields, fields => {
71-
fields[id] = undefined;
116+
this.state = produce(this.state, state => {
117+
state.fields[id] = undefined;
72118
});
73119
this.emit();
74120
}
75121

76122
public updateValue(fieldId: string, value: string): void {
77-
if (this.fields[fieldId] == null) {
123+
console.log(`Value updated ${fieldId}`);
124+
if (this.state.fields[fieldId] == null) {
78125
throw new Error(
79126
`Cannot update non-existent field value. (field id '${fieldId}').`
80127
);
81128
}
82-
this.fields = produce(this.fields, fields => {
83-
const field = fields[fieldId];
129+
this.state = produce(this.state, state => {
130+
const field = state.fields[fieldId];
84131
if (field == null) {
85132
return;
86133
}
87134

88135
// Value equality check should be abstracted for particular component and value type
89136
if (field.currentValue !== value) {
90-
field.isTouched = true;
137+
field.status.isTouched = true;
91138
}
92139
field.currentValue = value;
93140
});
@@ -96,41 +143,69 @@ export class GroupStoreMutable extends TinyEmitter<Callback> {
96143
}
97144

98145
public focus(fieldId: string): void {
146+
console.log(`Focus ${fieldId}`);
99147
this.setFocused(fieldId, true);
100148
this.emit();
101149
}
102150

103151
public blur(fieldId: string): void {
152+
console.log(`Blur ${fieldId}`);
104153
this.setFocused(fieldId, false);
105154
this.emit();
106155
}
107156

108157
private setFocused(fieldId: string, isFocused: boolean): void {
109-
console.log(`Setting focus for field '${fieldId}' to ${isFocused}`);
110-
if (this.fields[fieldId] == null) {
158+
if (this.state.fields[fieldId] == null) {
111159
throw new Error(
112160
`Cannot update non-existent field value. (field id '${fieldId}').`
113161
);
114162
}
115-
this.fields = produce(this.fields, fields => {
116-
const field = fields[fieldId];
117-
if (field == null) {
163+
164+
this.state = produce(this.state, state => {
165+
const field = state.fields[fieldId];
166+
if (field == null || field.status.isFocused === isFocused) {
118167
return;
119168
}
120-
field.isFocused = isFocused;
169+
170+
field.status.isFocused = isFocused;
121171

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

129203
public toObject(): unknown {
130204
const result: { [key: string]: unknown } = {};
131-
for (const key in this.fields) {
132-
if (this.fields.hasOwnProperty(key)) {
133-
const field = this.fields[key];
205+
const fields = this.state.fields;
206+
for (const key in fields) {
207+
if (fields.hasOwnProperty(key)) {
208+
const field = fields[key];
134209
if (field == null) {
135210
continue;
136211
}

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)