Skip to content

Commit

Permalink
fix: preserve form input values on custom form validation (#91)
Browse files Browse the repository at this point in the history
resolves #58
  • Loading branch information
joshuagraber authored Jun 27, 2024
1 parent f701e72 commit aeaefc3
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 65 deletions.
72 changes: 30 additions & 42 deletions src/components/Form/PdapForm.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
<template>
<form
:id="id"
:name="name"
class="pdap-form"
@change="change"
@submit.prevent="submit($event)"
>
<form :id="id" :name="name" class="pdap-form" @submit.prevent="submit">
<div
v-if="typeof errorMessage === 'string'"
class="pdap-form-error-message"
Expand All @@ -23,7 +17,7 @@
: ''
"
:value="values[field.name]"
@change="updateForm(field.name, $event)"
@input="updateForm(field, $event)"
/>
<slot />
</form>
Expand All @@ -42,11 +36,12 @@ import { createRule } from '../../utils/vuelidate';
// Types
import { PdapFormProps } from './types';
import { PdapInputTypes } from '../Input/types';
import { PdapInputProps, PdapInputTypes } from '../Input/types';
// Props
const props = withDefaults(defineProps<PdapFormProps>(), {
error: null,
resetOn: 'submit',
});
// Emits
Expand Down Expand Up @@ -97,16 +92,20 @@ const v$ = useVuelidate(rules, values, { $autoDirty: false, $lazy: true });
const errorMessage = ref(props.error);
// Handlers
function updateForm(fieldName: string, event: Event) {
function updateForm(field: PdapInputProps, event: Event) {
const target = event.target as HTMLInputElement;
const update =
target.type === PdapInputTypes.CHECKBOX &&
typeof target.checked === 'boolean'
? target.checked.toString()
: target.value;
values.value[fieldName] = update;
change();
const update = (() => {
switch (field.type) {
case PdapInputTypes.CHECKBOX:
return target.checked ? 'true' : 'false';
default:
return target.value;
}
})();
values.value[field.name] = update;
emit('change', values.value);
}
// Effects
Expand All @@ -119,43 +118,32 @@ watchEffect(() => {
errorMessage.value = 'Please update this form to correct the errors';
});
// Effect - Debug logger - following comment ignores in coverage report
/* c8 ignore next 12 */
watchEffect(() => {
if (import.meta.env.MODE === 'development') {
console.debug(`PdapForm ${props.name}\n`, {
errorMessage: errorMessage.value,
props,
values,
vuelidate: {
rules,
v$,
},
});
if (props.resetOn && props.resetOn !== 'submit') {
resetForm();
}
});
function change() {
emit('change', { ...values.value });
/**
* Reset vuelidate and wipe values state
*/
function resetForm() {
v$.value.$reset();
values.value = Object.entries(values).reduce((acc, [key]) => {
return { ...acc, [key]: '' };
}, {});
}
async function submit(event: Event) {
async function submit() {
// Check form submission
const isValidSubmission = await v$.value.$validate();
if (isValidSubmission) {
// Emit submit event (spread to new object to create new object, this allows us to reset `values` without messing with the data returned)
emit('submit', { ...values.value });
// Reset vuelidate and form
v$.value.$reset();
const form = <HTMLFormElement>event.target;
form.reset();
// Wipe values state
values.value = Object.entries(values).reduce((acc, [key]) => {
return { ...acc, [key]: '' };
}, {});
return;
if (props.resetOn === 'submit') {
resetForm();
}
}
}
</script>
Expand Down
15 changes: 9 additions & 6 deletions src/components/Form/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ The `Form` component is powerful. All you need to do is pass a few props, and th

## Props

| name | required? | types | description | default |
| -------- | --------- | -------------------------------------------------------- | ---------------------------------- | ----------- |
| `error` | no | `string` \| `undefined` \| `null` | Error state | `undefined` |
| `id` | yes | `string` | Form id | none |
| `name` | yes | `string` | Form name | none |
| `schema` | yes | `PdapFormSchema` (array of `PdapFormInputProps` objects) | Array of schema entries for inputs | none |
| name | required? | types | description | default |
| --------- | --------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ----------- |
| `error` | no | `string` \| `undefined` \| `null` | Error state | `undefined` |
| `id` | yes | `string` | Form id | none |
| `name` | yes | `string` | Form name | none |
| `schema` | yes | `PdapFormSchema` (array of `PdapFormInputProps` objects) | Array of schema entries for inputs | none |
| `resetOn` | no | `'submit'` \| `boolean` | When to reset form - if `'submit'`, it happens during submission. If `boolean`, it happens on any change of the prop to `true` | `'submit'` |



Expand Down Expand Up @@ -71,6 +72,8 @@ PdapFormValidators {

## Example

Note: to observe `resetOn`, see the [demo page](../../demo/pages/SignupFormDemo.vue)

```
<template>
<Form :schema="formSchema" :on-submit="handleSubmit" id="test-form" name="data-request-form">
Expand Down
22 changes: 20 additions & 2 deletions src/components/Form/form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ describe('Form component', () => {
await inputTextTwo.setValue('bar');
await inputEmail.setValue('[email protected]');
await inputPassword.setValue('P@ssword1!');
await inputCheckboxDefaultChecked.trigger('change');
await inputCheckboxDefaultUnchecked.trigger('change');
await inputCheckboxDefaultChecked.trigger('input');
await inputCheckboxDefaultUnchecked.trigger('input');
await nextTick();

// Assert new values
Expand Down Expand Up @@ -288,4 +288,22 @@ describe('Form component', () => {
// Assert error message no longer present
expect(wrapper.find('.pdap-form-error-message').exists()).toBe(false);
});

test('Form waits to reset until resetOn prop switches to `true` and error is falsy', async () => {
const wrapper = mount(PdapForm, {
...base,
props: { ...base.props, error: 'foo', resetOn: false },
});

// Assert error state
expect(wrapper.find('.pdap-form-error-message').exists()).toBe(true);
// Assert error message
expect(wrapper.find('.pdap-form-error-message').text()).toBe('foo');

// Set values to correct values
await wrapper.setProps({ error: '', resetOn: true });

// Assert error message no longer present
expect(wrapper.find('.pdap-form-error-message').exists()).toBe(false);
});
});
1 change: 1 addition & 0 deletions src/components/Form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ export interface PdapFormProps {
id: string;
name: string;
schema: PdapFormSchema;
resetOn?: 'submit' | boolean;
}
1 change: 0 additions & 1 deletion src/components/Header/PdapHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ const navLogoLinkIsPath = props.logoImageAnchorPath.startsWith('/');
// Lifecycle methods
onMounted(() => {
console.debug('on mounted');
getHeightAndSetToCSSVar();
window.addEventListener('resize', getHeightAndSetToCSSVar);
});
Expand Down
8 changes: 4 additions & 4 deletions src/components/Input/Checkbox/InputCheckbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class="pdap-input-checkbox"
type="checkbox"
v-bind="$attrs"
@change="change"
@input="input"
/>
</template>

Expand All @@ -18,10 +18,10 @@ defineProps<PdapInputCheckboxProps>();
// Emits
const emit = defineEmits<{
(event: 'change', val: Event): void;
(event: 'input', val: Event): void;
}>();
const change = (event: Event) => {
emit('change', event);
const input = (event: Event) => {
emit('input', event);
};
</script>
4 changes: 2 additions & 2 deletions src/components/Input/PdapInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const errorMessageId = computed(() => `pdap-${props.name}-input-error`);
@layer components {
/* General input styles */
.pdap-input {
@apply h-[max-content] gap-4 leading-normal mb-3 w-full flex flex-col;
@apply h-[max-content] gap-1 leading-normal mb-3 w-full flex flex-col;
}
.pdap-input input {
Expand Down Expand Up @@ -78,7 +78,7 @@ const errorMessageId = computed(() => `pdap-${props.name}-input-error`);
}
.pdap-input-error label {
@apply justify-start text-sm;
@apply justify-start;
}
.pdap-input-error input {
Expand Down
6 changes: 0 additions & 6 deletions src/components/Input/Text/InputText.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
:placeholder="placeholder"
:value="value"
v-bind="$attrs"
@change="change"
@input="input"
/>
</template>
Expand All @@ -18,14 +17,9 @@ defineProps<PdapInputTextProps>();
// Emits
const emit = defineEmits<{
(event: 'change', val: Event): void;
(event: 'input', val: Event): void;
}>();
const change = (event: Event) => {
emit('change', event);
};
const input = (event: Event) => {
emit('input', event);
};
Expand Down
6 changes: 4 additions & 2 deletions src/demo/pages/ComponentDemo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<div>
<h4>Or this:</h4>
<div
class="loading-shimmer h-48 bg-brand-gold flex flex-col justify-center items-center"
class="loading-shimmer h-48 bg-brand-gold flex flex-col justify-center items-center text-neutral-50"
>
<Spinner :show="true" text="Hello from the loading div" />
</div>
Expand Down Expand Up @@ -239,6 +239,7 @@ import { PdapDropdownTriggerType } from '../../components/Dropdown/types';
const mockFormSchema = [
{
autocomplete: 'given-name',
id: 'first-name',
name: 'firstName',
label: 'What is your first name?',
Expand All @@ -252,6 +253,7 @@ const mockFormSchema = [
},
},
{
autocomplete: 'family-name',
id: 'last-name',
name: 'lastName',
label: 'What is your last name?',
Expand Down Expand Up @@ -339,7 +341,7 @@ function submit(values: Record<'firstName' | 'lastName' | 'iceCream', string>) {
}
function change(values: Record<'firstName' | 'lastName' | 'iceCream', string>) {
console.log('onChange', { values });
console.debug('onChange', { values });
}
onMounted(updateLoadingText);
Expand Down
Loading

0 comments on commit aeaefc3

Please sign in to comment.