diff --git a/README.md b/README.md
index afeb1b3..844bff3 100644
--- a/README.md
+++ b/README.md
@@ -387,6 +387,65 @@ Please note that when `formState.setField` is called, any existing errors that m
It's also possible to set the error value for a single input using `formState.setFieldError` and to clear a single input's value using `formState.clearField`.
+### Updating multiple fields at once
+
+Updating the value of multiple fields in the form at once is possible via the `formState.setFields` method.
+
+This could come in handy if you're, for example, loading data from the server.
+
+```js
+function Form() {
+ const [formState, { text, email }] = useFormState();
+
+ React.useEffect(function loadDataFromServer() {
+ // we'll simulate some delay with a setTimeout. This could be your fetch() request:
+ const timer = setTimeout(() => {
+ formState.setFields({
+ name: "John",
+ age: 24,
+ email: "john@example.com"
+ });
+ }, 1000);
+ return () => clearTimeout(timer);
+ }, []);
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
+
+```
+
+`formState.setFields` has a second `options` argument which can be used to update the `touched`, `validity` and `errors` in the state.
+
+`touched` and `validity` can be a boolean value which applies that value to all fields for which a value is provided.
+
+```js
+// mark all fields as valid (clears all errors):
+formState.setFields(newValues, {
+ validity: true
+});
+
+// marks only "name" as invalid:
+formState.setFields(newValues, {
+ validity: {
+ name: false
+ },
+ errors: {
+ name: "Your name is required!"
+ }
+});
+
+// marks all fields as not touched:
+formState.setFields(newValues, {
+ touched: false
+});
+```
+
### Resetting The From State
All fields in the form can be cleared all at once at any time using `formState.clear`.
@@ -601,6 +660,9 @@ formState = {
// updates the value of an input
setField(name: string, value: string): void,
+ // updates multiple field values and (optionally) sets touched, validity and errors:
+ setFields(values: object, [options]: object): void,
+
// sets the error of an input
setFieldError(name: string, error: string): void,
}
diff --git a/src/index.d.ts b/src/index.d.ts
index b029603..00e041e 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -18,6 +18,12 @@ interface UseFormStateHook {
export const useFormState: UseFormStateHook;
+type SetFieldsOptions = {
+ touched?: StateValidity;
+ validity?: StateValidity;
+ errors?: StateErrors;
+};
+
interface FormState> {
values: StateValues;
validity: StateValidity;
@@ -25,6 +31,7 @@ interface FormState> {
errors: E;
clear(): void;
setField(name: K, value: T[K]): void;
+ setFields(fieldValues: StateValues, options?: SetFieldsOptions): void;
setFieldError(name: keyof T, error: string): void;
clearField(name: keyof T): void;
}
diff --git a/src/useState.js b/src/useState.js
index d726ef4..1253bbb 100644
--- a/src/useState.js
+++ b/src/useState.js
@@ -23,6 +23,10 @@ export function useState({ initialState, onClear }) {
const clearField = name => setField(name);
+ function setAll(fields, value) {
+ return fields.reduce((obj, name) => Object.assign(obj, {[name]: value}), {});
+ }
+
return {
/**
* @type {{ values, touched, validity, errors }}
@@ -43,6 +47,60 @@ export function useState({ initialState, onClear }) {
setField(name, value) {
setField(name, value, true, true);
},
+ setFields(fieldValues, options = {touched: false, validity: true}) {
+ setValues(fieldValues);
+
+ if (options) {
+ const fields = Object.keys(fieldValues);
+
+ if (options.touched !== undefined) {
+ // We're setting the touched state of all fields at once:
+ if (typeof options.touched === "boolean") {
+ setTouched(setAll(fields, options.touched));
+ } else {
+ setTouched(options.touched);
+ }
+ }
+
+ if (options.validity !== undefined) {
+ if (typeof options.validity === "boolean") {
+ // We're setting the validity of all fields at once:
+ setValidity(setAll(fields, options.validity));
+ if (options.validity) {
+ // All fields are valid, clear the errors:
+ setError(setAll(fields, undefined));
+ }
+ } else {
+ setValidity(options.validity);
+
+ if (options.errors === undefined) {
+ // Clear the errors for valid fields:
+ const errorFields = Object.entries(options.validity).reduce((errorsObj, [name, isValid]) => {
+ if (isValid) {
+ return Object.assign({}, errorsObj || {}, {[name]: undefined});
+ }
+ return errorsObj;
+ }, null);
+
+ if (errorFields) {
+ setError(errorFields);
+ }
+ }
+ }
+ }
+
+ if (options.errors) {
+ // Not logical to set the same error for all fields so has to be an object.
+ setError(options.errors);
+
+ if (options.validity === undefined) {
+ // Fields with errors are not valid:
+ setValidity(setAll(Object.keys(options.errors), false));
+ }
+ }
+ }
+
+ },
setFieldError(name, error) {
setValidity({ [name]: false });
setError({ [name]: error });
diff --git a/test/useFormState-manual-updates.test.js b/test/useFormState-manual-updates.test.js
index f5112eb..9086745 100644
--- a/test/useFormState-manual-updates.test.js
+++ b/test/useFormState-manual-updates.test.js
@@ -56,6 +56,173 @@ describe('useFormState manual updates', () => {
expect(formState.current.values.name).toBe('waseem');
});
+ it('sets the values of multiple inputs using formState.setFields', () => {
+ const { formState } = renderWithFormState(([, input]) => (
+ <>
+
+
+
+ >
+ ));
+
+ const values1 = {
+ firstName: "John",
+ lastName: "Doe",
+ age: 33
+ };
+
+ formState.current.setFields(values1);
+
+ expect(formState.current.values).toMatchObject(values1);
+ expect(Object.values(formState.current.validity)).toMatchObject([true, true, true]);
+ expect(Object.values(formState.current.touched)).toMatchObject([false, false, false]);
+ expect(Object.values(formState.current.errors)).toMatchObject([undefined, undefined, undefined]);
+
+ const values2 = {
+ firstName: "Barry",
+ lastName: ""
+ };
+
+ formState.current.setFields(values2);
+
+ const expected2 = Object.assign({}, values1, values2);
+ expect(formState.current.values).toMatchObject(expected2);
+ });
+
+ it('sets validity when provided in options of formState.setFields', () => {
+ const { formState } = renderWithFormState(([, input]) => (
+ <>
+
+
+ >
+ ));
+
+ const values = {
+ firstName: "John",
+ lastName: "Doe"
+ };
+
+ formState.current.setFields(values, {
+ validity: true
+ });
+ expect(formState.current.values).toMatchObject(values);
+ expect(formState.current.validity).toMatchObject({
+ firstName: true,
+ lastName: true
+ });
+
+ formState.current.setFields({firstName: "test", lastName: "foo"}, {
+ validity: false
+ });
+ expect(formState.current.validity).toMatchObject({
+ firstName: false,
+ lastName: false
+ });
+
+ formState.current.setFields(values, {
+ validity: {
+ firstName: true,
+ lastName: false
+ }
+ });
+ expect(Object.values(formState.current.validity)).toMatchObject([true, false]);
+ });
+
+ it('sets touched when provided in options of formState.setFields', () => {
+ const { formState } = renderWithFormState(([, input]) => (
+ <>
+
+
+ >
+ ));
+
+ const values = {
+ firstName: "John",
+ lastName: "Doe"
+ };
+
+ formState.current.setFields(values, {
+ touched: true
+ });
+ expect(formState.current.values).toMatchObject(values);
+ expect(formState.current.touched).toMatchObject({
+ firstName: true,
+ lastName: true
+ });
+
+ formState.current.setFields(values, {
+ touched: false
+ });
+ expect(formState.current.touched).toMatchObject({
+ firstName: false,
+ lastName: false
+ });
+
+ formState.current.setFields(values, {
+ touched: {
+ firstName: true,
+ lastName: false
+ }
+ });
+ expect(formState.current.touched).toMatchObject({
+ firstName: true,
+ lastName: false
+ });
+ });
+
+ it('sets the errors of the specified fields when provided in options of formState.setFields', () => {
+ const { formState } = renderWithFormState(([, input]) => (
+ <>
+
+
+ >
+ ));
+
+ const values = {
+ firstName: "John",
+ lastName: ""
+ };
+ const errors = {
+ lastName: "This field cannot be empty"
+ };
+
+ formState.current.setFields(values, {errors});
+
+ expect(formState.current.errors).toMatchObject(errors);
+ expect(formState.current.validity).toMatchObject({
+ lastName: false
+ });
+ });
+
+ it ('automatically clears errors when marking fields as valid via formState.setFields', () => {
+ const { formState } = renderWithFormState(([, input]) => (
+ <>
+
+ >
+ ));
+
+ const values = {
+ firstName: "#$%^&"
+ };
+ const errors = {
+ firstName: "That's not a name"
+ };
+ formState.current.setFields(values, {errors});
+ expect(formState.current.values).toMatchObject(values);
+ expect(formState.current.errors).toMatchObject(errors);
+
+ const newValues = {
+ firstName: "John"
+ };
+ const newValidity = {
+ firstName: true
+ };
+ formState.current.setFields(newValues, {validity: newValidity});
+
+ expect(formState.current.values).toMatchObject(newValues);
+ expect(formState.current.errors).toMatchObject({firstName: undefined});
+ });
+
it('sets the error of an input and invalidates the input programmatically using from.setFieldError', () => {
const { formState } = renderWithFormState(([, input]) => (