Skip to content

Commit bc785e9

Browse files
Copilotjpaladin
andcommitted
Change dirty to computed property, remove isDirty
Co-authored-by: jpaladin <12514334+jpaladin@users.noreply.github.com>
1 parent a281dc9 commit bc785e9

File tree

6 files changed

+29
-44
lines changed

6 files changed

+29
-44
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515

1616
* Keeps form's state and validation results
1717
* Supports any kind of validation functions
18-
* Dirty checking (tracks both user interactions and value changes from initial state)
19-
* Track changes from initial values with `isDirty`
18+
* Dirty checking (tracks value changes from initial state)
2019
* Separates data from view
2120
* Relies on hooks, but can easily be used with class components
2221

@@ -88,7 +87,7 @@ Hook that keeps on form field's data.
8887

8988
| Type <div style="width: 200px"></div> | Description |
9089
|---- | ----------- |
91-
| _{<br>&nbsp;&nbsp;&nbsp;value: any,<br>&nbsp;&nbsp;&nbsp;error: boolean<br>&nbsp;&nbsp;&nbsp;dirty: boolean,<br>&nbsp;&nbsp;&nbsp;isDirty: boolean,<br>&nbsp;&nbsp;&nbsp;onChange: (any, config?) => void<br>&nbsp;&nbsp;&nbsp;onBlur: (event, config?) => void<br>&nbsp;&nbsp;&nbsp;setValue: (value: any) => void<br>&nbsp;&nbsp;&nbsp;validate: (any, config?) => boolean or Promise&lt;boolean&gt;<br>&nbsp;&nbsp;&nbsp;reset: () => void,<br>&nbsp;&nbsp;&nbsp;props: {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value: any,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;onChange: (any, config?) => void<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;onBlur: (event, config?) => void<br>&nbsp;&nbsp;&nbsp;}<br>}_ | Object with field's data and callbacks.<br><br><ul><li>`value` - field's current value</li><li>`error` - is error present flag (`true` if value was validated and didn't pass validation, `false` otherwise)</li><li>`dirty` - indicates whether value of field was changed by user (used internally for validation timing)</li><li>`isDirty` - indicates whether current value differs from initial value (useful for enabling Save buttons)</li><li>`onChange` - callback for change event (changes the value and validates it if previous value wasn't correct)</li><li>`onBlur` - callback for blur event (validates the value)</li><li>`setValue` - function for setting the internal value (does not validate the input, enabling support for async data loading; also updates the initial value so `isDirty` remains `false`)</li><li>`validate` - function for validating field's value</li><li>`reset` - function for resetting field's data</li><li>`props` - set of props that can be spread on standard input elements (same as props in root object, just grouped for better DX)</li></ul><br/>`onChange`, `onBlur` and `validate` functions accept config as last parameter - this will override config from `useValidation` if provided. |
90+
| _{<br>&nbsp;&nbsp;&nbsp;value: any,<br>&nbsp;&nbsp;&nbsp;error: boolean<br>&nbsp;&nbsp;&nbsp;dirty: boolean,<br>&nbsp;&nbsp;&nbsp;onChange: (any, config?) => void<br>&nbsp;&nbsp;&nbsp;onBlur: (event, config?) => void<br>&nbsp;&nbsp;&nbsp;setValue: (value: any) => void<br>&nbsp;&nbsp;&nbsp;validate: (any, config?) => boolean or Promise&lt;boolean&gt;<br>&nbsp;&nbsp;&nbsp;reset: () => void,<br>&nbsp;&nbsp;&nbsp;props: {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value: any,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;onChange: (any, config?) => void<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;onBlur: (event, config?) => void<br>&nbsp;&nbsp;&nbsp;}<br>}_ | Object with field's data and callbacks.<br><br><ul><li>`value` - field's current value</li><li>`error` - is error present flag (`true` if value was validated and didn't pass validation, `false` otherwise)</li><li>`dirty` - indicates whether current value differs from initial value (useful for enabling Save buttons and tracking changes)</li><li>`onChange` - callback for change event (changes the value and validates it if previous value wasn't correct)</li><li>`onBlur` - callback for blur event (validates the value)</li><li>`setValue` - function for setting the internal value (does not validate the input, enabling support for async data loading; also updates the initial value so `dirty` remains `false`)</li><li>`validate` - function for validating field's value</li><li>`reset` - function for resetting field's data</li><li>`props` - set of props that can be spread on standard input elements (same as props in root object, just grouped for better DX)</li></ul><br/>`onChange`, `onBlur` and `validate` functions accept config as last parameter - this will override config from `useValidation` if provided. |
9291

9392
#### Usage example
9493

@@ -272,7 +271,7 @@ Util function for checking if any field in a form has been changed from its init
272271

273272
| Name | Type <div style="width: 200px"></div> | Required | Description |
274273
| ---- | ---- | ---- | ----------- |
275-
| fields | _{<br/>&nbsp;&nbsp;key: { isDirty: boolean },<br/>&nbsp;&nbsp;...<br/>}_ | yes | Form field's data (each field must have `isDirty` property - other properties are not important) |
274+
| fields | _{<br/>&nbsp;&nbsp;key: { dirty: boolean },<br/>&nbsp;&nbsp;...<br/>}_ | yes | Form field's data (each field must have `dirty` property - other properties are not important) |
276275

277276
#### Returns
278277

cypress/components/isDirty.cy.jsx

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ describe('isDirty functionality', () => {
1010

1111
return (
1212
<div>
13-
<p data-testid="isDirty">{field.isDirty.toString()}</p>
13+
<p data-testid="dirty">{field.dirty.toString()}</p>
1414
</div>
1515
);
1616
};
1717

1818
mount(<Component />);
19-
cy.get('[data-testid="isDirty"]').should('have.text', 'false');
19+
cy.get('[data-testid="dirty"]').should('have.text', 'false');
2020
});
2121

2222
it('should be true after onChange', () => {
@@ -26,15 +26,15 @@ describe('isDirty functionality', () => {
2626
return (
2727
<div>
2828
<input data-testid="input" {...field.props} />
29-
<p data-testid="isDirty">{field.isDirty.toString()}</p>
29+
<p data-testid="dirty">{field.dirty.toString()}</p>
3030
</div>
3131
);
3232
};
3333

3434
mount(<Component />);
35-
cy.get('[data-testid="isDirty"]').should('have.text', 'false');
35+
cy.get('[data-testid="dirty"]').should('have.text', 'false');
3636
cy.get('[data-testid="input"]').clear().type('changed');
37-
cy.get('[data-testid="isDirty"]').should('have.text', 'true');
37+
cy.get('[data-testid="dirty"]').should('have.text', 'true');
3838
});
3939

4040
it('should be false after onChange back to initial value', () => {
@@ -44,17 +44,17 @@ describe('isDirty functionality', () => {
4444
return (
4545
<div>
4646
<input data-testid="input" {...field.props} />
47-
<p data-testid="isDirty">{field.isDirty.toString()}</p>
47+
<p data-testid="dirty">{field.dirty.toString()}</p>
4848
</div>
4949
);
5050
};
5151

5252
mount(<Component />);
53-
cy.get('[data-testid="isDirty"]').should('have.text', 'false');
53+
cy.get('[data-testid="dirty"]').should('have.text', 'false');
5454
cy.get('[data-testid="input"]').clear().type('changed');
55-
cy.get('[data-testid="isDirty"]').should('have.text', 'true');
55+
cy.get('[data-testid="dirty"]').should('have.text', 'true');
5656
cy.get('[data-testid="input"]').clear().type('initial');
57-
cy.get('[data-testid="isDirty"]').should('have.text', 'false');
57+
cy.get('[data-testid="dirty"]').should('have.text', 'false');
5858
});
5959

6060
it('should remain false after setValue', () => {
@@ -72,17 +72,17 @@ describe('isDirty functionality', () => {
7272
<div>
7373
<input data-testid="input" {...field.props} />
7474
<button data-testid="button" onClick={() => setClicked(true)}>Set Value</button>
75-
<p data-testid="isDirty">{field.isDirty.toString()}</p>
75+
<p data-testid="dirty">{field.dirty.toString()}</p>
7676
<p data-testid="value">{field.value}</p>
7777
</div>
7878
);
7979
};
8080

8181
mount(<Component />);
82-
cy.get('[data-testid="isDirty"]').should('have.text', 'false');
82+
cy.get('[data-testid="dirty"]').should('have.text', 'false');
8383
cy.get('[data-testid="button"]').click();
8484
cy.get('[data-testid="value"]').should('have.text', 'newInitial');
85-
cy.get('[data-testid="isDirty"]').should('have.text', 'false');
85+
cy.get('[data-testid="dirty"]').should('have.text', 'false');
8686
});
8787

8888
it('should be true after setValue then onChange', () => {
@@ -100,16 +100,16 @@ describe('isDirty functionality', () => {
100100
<div>
101101
<input data-testid="input" {...field.props} />
102102
<button data-testid="button" onClick={() => setClicked(true)}>Set Value</button>
103-
<p data-testid="isDirty">{field.isDirty.toString()}</p>
103+
<p data-testid="dirty">{field.dirty.toString()}</p>
104104
</div>
105105
);
106106
};
107107

108108
mount(<Component />);
109109
cy.get('[data-testid="button"]').click();
110-
cy.get('[data-testid="isDirty"]').should('have.text', 'false');
110+
cy.get('[data-testid="dirty"]').should('have.text', 'false');
111111
cy.get('[data-testid="input"]').clear().type('changed');
112-
cy.get('[data-testid="isDirty"]').should('have.text', 'true');
112+
cy.get('[data-testid="dirty"]').should('have.text', 'true');
113113
});
114114

115115
it('should be false after reset', () => {
@@ -120,16 +120,16 @@ describe('isDirty functionality', () => {
120120
<div>
121121
<input data-testid="input" {...field.props} />
122122
<button data-testid="reset" onClick={() => field.reset()}>Reset</button>
123-
<p data-testid="isDirty">{field.isDirty.toString()}</p>
123+
<p data-testid="dirty">{field.dirty.toString()}</p>
124124
</div>
125125
);
126126
};
127127

128128
mount(<Component />);
129129
cy.get('[data-testid="input"]').clear().type('changed');
130-
cy.get('[data-testid="isDirty"]').should('have.text', 'true');
130+
cy.get('[data-testid="dirty"]').should('have.text', 'true');
131131
cy.get('[data-testid="reset"]').click();
132-
cy.get('[data-testid="isDirty"]').should('have.text', 'false');
132+
cy.get('[data-testid="dirty"]').should('have.text', 'false');
133133
});
134134
});
135135

cypress/components/isDirtyIntegration.cy.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('isDirty integration test - Complete workflow', () => {
3636
>
3737
Save
3838
</button>
39-
<p data-testid="hasChanges">{hasChanges.toString()}</p>
39+
<p data-testid="dirty">{hasChanges.toString()}</p>
4040
</>
4141
)}
4242
</div>
@@ -51,21 +51,21 @@ describe('isDirty integration test - Complete workflow', () => {
5151
// After data loads, button should be disabled (no changes yet)
5252
cy.wait(150);
5353
cy.get('[data-testid="save"]').should('be.disabled');
54-
cy.get('[data-testid="hasChanges"]').should('have.text', 'false');
54+
cy.get('[data-testid="dirty"]').should('have.text', 'false');
5555

5656
// After user makes a change, button should be enabled
5757
cy.get('[data-testid="name"]').clear().type('Jane Doe');
5858
cy.get('[data-testid="save"]').should('not.be.disabled');
59-
cy.get('[data-testid="hasChanges"]').should('have.text', 'true');
59+
cy.get('[data-testid="dirty"]').should('have.text', 'true');
6060

6161
// After reverting to original value, button should be disabled again
6262
cy.get('[data-testid="name"]').clear().type('John Doe');
6363
cy.get('[data-testid="save"]').should('be.disabled');
64-
cy.get('[data-testid="hasChanges"]').should('have.text', 'false');
64+
cy.get('[data-testid="dirty"]').should('have.text', 'false');
6565

6666
// Changing another field should enable the button
6767
cy.get('[data-testid="email"]').clear().type('jane@example.com');
6868
cy.get('[data-testid="save"]').should('not.be.disabled');
69-
cy.get('[data-testid="hasChanges"]').should('have.text', 'true');
69+
cy.get('[data-testid="dirty"]').should('have.text', 'true');
7070
});
7171
});

src/formUtils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export const validateFields = (fields) => {
7676
* @returns {boolean} true if any field is dirty, false otherwise
7777
*/
7878
export const isDirty = (fields) => {
79-
return Object.values(fields).some(field => field.isDirty);
79+
return Object.values(fields).some(field => field.dirty);
8080
};
8181

8282
/**

src/useValidation.js

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,15 @@ export const useValidation = (defaultValue, validationFn, config) => {
2929
const [resetToValue, setResetToValue] = useState(defaultValue);
3030
const [value, setValue] = useState(resetToValue);
3131
const [error, setError] = useState(false);
32-
const [dirty, setDirty] = useState(false);
3332

34-
// isDirty compares current value with initial value
35-
const isDirty = value !== resetToValue;
33+
// dirty compares current value with initial value
34+
const dirty = value !== resetToValue;
3635

3736
const onChange = (e, config) => {
3837
const activeConfig = config ?? _config;
3938
const v = activeConfig.receiveEvent ? e.target.value : e;
4039
setValue(v);
4140

42-
// Setting dirty flag indicates that value has been changed
43-
if (!activeConfig.ignoreDirtiness && !dirty) {
44-
setDirty(true);
45-
}
46-
4741
// Value is validated on change, only if previously was incorrect
4842
if (error) {
4943
validate(v, activeConfig);
@@ -57,11 +51,6 @@ export const useValidation = (defaultValue, validationFn, config) => {
5751
if (activeConfig.ignoreDirtiness || dirty) {
5852
validate(value, activeConfig);
5953
}
60-
61-
// Resets the dirty flag
62-
if (!activeConfig.ignoreDirtiness && !dirty) {
63-
setDirty(false);
64-
}
6554
};
6655

6756
const _handleSetValue = (v) => {
@@ -94,14 +83,12 @@ export const useValidation = (defaultValue, validationFn, config) => {
9483
const reset = () => {
9584
setValue(defaultValue);
9685
setError(false);
97-
setDirty(false);
9886
};
9987

10088
return {
10189
value,
10290
error,
10391
dirty,
104-
isDirty,
10592
onChange,
10693
onBlur,
10794
setValue: _handleSetValue,

types/index.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export declare interface Field {
88
value: any,
99
error: boolean,
1010
dirty: boolean,
11-
isDirty: boolean,
1211
setValue: (v: any) => void,
1312
onChange: (v: any, config?: FieldConfig) => void,
1413
onBlur: (event: any, config?: FieldConfig) => void,

0 commit comments

Comments
 (0)