Skip to content

Commit

Permalink
add onChange method (#37)
Browse files Browse the repository at this point in the history
* add onChange method

* change model to form type

* add tests for onChange method
  • Loading branch information
grcldq authored Jan 26, 2024
1 parent ff5d659 commit 7444c15
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 7 deletions.
5 changes: 3 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -721,9 +721,10 @@ return (
| Property | Details |
| ------------- | ---------------------------------------------------------------------------------------------------------------- |
| model | Your form model, it should be an object (can be empty). Every property will be mapped into a field. |
| validations | A validations object. |
| onSubmit | Your custom submit function. It will be parsed internally and provide a onSubmit handler to call programaticaly. |
| onSubmitError | A useful handler to deal with errors. |
| validations | A validations object. |
| onChange | A handler for changes in all form fields. |
<p>&nbsp</p>
Expand Down Expand Up @@ -818,7 +819,7 @@ Releases are done via GitHub Releases. Create a new release there, this will aut
### Sandbox App
There is a also a sandbox basic application to play around with the library. Use `yarn dev` to start up the parcel server, and you can find the files inside the `/sandbox-app` folder.
There is a also a sandbox basic application to play around with the library. Use `yarn start` to start up the parcel server, and you can find the files inside the `/sandbox-app` folder.
#### Troubleshoot
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
},
"contributors": [
"Leo Gonzalez",
"Jens Ravens"
"Jens Ravens",
"Geraldine Atayan"
],
"license": "MIT",
"devDependencies": {
Expand Down
5 changes: 4 additions & 1 deletion sandbox-app/simple-object.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { useForm } from '../src';
import { Input } from './components/Input';

interface Model {
simpleObject?: {
Expand All @@ -27,6 +26,10 @@ export function SimpleObject(): JSX.Element {
// eslint-disable-next-line no-console
console.log(model);
},
onChange: ({ model }) => {
// eslint-disable-next-line no-console
console.log(model);
},
});

return (
Expand Down
29 changes: 28 additions & 1 deletion src/form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,23 @@ function createForm({
onSubmit,
onSubmitError,
onInit,
onChange,
}: {
value?: Partial<Model>;
validations?: Partial<MappedValidation<Model>>;
onSubmit?: (form: Form<Model>) => Promise<void> | void;
onSubmitError?: (error: Error) => void;
onInit?: (form: Form<Model>) => void;
onChange?: (form: Form<Model>) => void;
} = {}): Form<Model> {
return new Form<Model>({
model: { ...defaultValue, ...(value || {}) },
validations,
onUpdate: tracker.onUpdate,
onSubmit: onSubmit ?? tracker.onSubmit,
onSubmitError,
onInit,
validations,
onChange,
});
}

Expand Down Expand Up @@ -826,4 +829,28 @@ describe(Form, () => {
expect(form.fields.address.dirty).toBeTruthy();
});
});

describe('change handler', () => {
it('invokes the onChange callback when a field changes value', () => {
const initializeAppSpy = jest.fn();
const form = createForm({
onChange: initializeAppSpy,
});
form.fields.name.onChange('Freddy');
form.fields.age.onChange(27);
expect(initializeAppSpy).toHaveBeenCalledTimes(2);
});

it('passes the form values on the onChange method', () => {
let storedFormValues = null;
const form = createForm({
onChange: (form) => {
storedFormValues = form;
},
});
form.fields.name.onChange('George');
form.fields.age.onChange(30);
expect(storedFormValues).toEqual(form);
});
});
});
7 changes: 6 additions & 1 deletion src/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ export class Form<T> {
onSubmit?: (form: Form<T>) => void | Promise<void>;
onSubmitError: ((error: Error) => void) | undefined;
#validations: MappedValidation<T>;
#onUpdate?: () => void;
#field: FieldImplementation<T, T>;
#onChange?: (form: Form<T>) => void;
#onUpdate?: () => void;

constructor({
model,
Expand All @@ -19,13 +20,15 @@ export class Form<T> {
onSubmit,
onSubmitError,
onInit,
onChange,
}: {
model: T;
validations?: MappedValidation<T>;
onUpdate?: () => void;
onSubmit?: (form: Form<T>) => void | Promise<void>;
onSubmitError?: (error: Error) => void;
onInit?: (form: Form<T>) => void;
onChange?: (form: Form<T>) => void;
}) {
this.#validations = validations ?? {};
this.#field = new FieldImplementation<T, T>({
Expand All @@ -40,6 +43,7 @@ export class Form<T> {
this.#onUpdate = onUpdate;
this.onSubmit = onSubmit;
this.onSubmitError = onSubmitError;
this.#onChange = onChange;
}

// This method will touch every field, for the purpose of displaying the errors in the view
Expand Down Expand Up @@ -149,6 +153,7 @@ export class Form<T> {
) {
this.submissionStatus = 'idle';
}
this.#onChange?.(this);
this.#onUpdate?.();
}
}
5 changes: 4 additions & 1 deletion src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface UseFormProps<T> {
onSubmit?: (form: Form<T>) => void | Promise<void>;
onSubmitError?: (error: Error) => void;
onInit?: (form: Form<T>) => void;
onChange?: (form: Form<T>) => void;
}

// This interface is what you get back from the useForm hook
Expand All @@ -36,18 +37,20 @@ export function useForm<T>({
onSubmitError,
validations,
onInit,
onChange,
_unstableUpdateModelOnChange,
}: UseFormProps<T>): FormModel<T> {
// Using a custom hook to call a rerender on every change
const onUpdate = useForceUpdate();
const formRef = useRef<Form<T>>(
new Form({
model,
onUpdate,
validations,
onUpdate,
onSubmit,
onSubmitError,
onInit,
onChange,
})
);
const form = formRef.current;
Expand Down

0 comments on commit 7444c15

Please sign in to comment.