Skip to content

Commit

Permalink
fix: keep field state on rerender
Browse files Browse the repository at this point in the history
Release-As: 3.0.0-alpha.10
  • Loading branch information
leonied7 committed Jan 23, 2024
1 parent 9d78554 commit b442d1f
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 7 deletions.
12 changes: 7 additions & 5 deletions src/components/ValidationField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
</template>

<script lang="ts" setup>
import { computed, inject, ref, toRefs, onBeforeUnmount, reactive, nextTick } from 'vue';
import { computed, inject, ref, toRefs, reactive, nextTick, onUnmounted } from 'vue';
import type { InnerValidationError } from '../types/error';
import {
getFieldDefaultValueSymbol,
getFieldPristineSymbol,
getFieldValueSymbol,
getIsSubmittedSymbol,
hasFieldValueSymbol,
Expand All @@ -45,13 +46,12 @@ const emit = defineEmits<{
const { name, isEqual } = toRefs(props);
const registered = ref(false);
const value = ref<unknown>();
const pristine = ref<boolean>(true);
const errors = ref<InnerValidationError[]>([]);
const hasFieldValue = inject(hasFieldValueSymbol)!;
const getFieldDefaultValue = inject(getFieldDefaultValueSymbol)!;
const getFieldValue = inject(getFieldValueSymbol)!;
const getFieldPristine = inject(getFieldPristineSymbol)!;
const getIsSubmitted = inject(getIsSubmittedSymbol)!;
const register = inject(registerSymbol)!;
const validate = inject(validateSymbol)!;
Expand All @@ -60,6 +60,9 @@ const defaultValue = computed(() => getFieldDefaultValue(name.value));
const hasProvidedValue = computed(() => hasFieldValue(name.value));
const providedValue = computed(() => getFieldValue(name.value));
const submitted = computed(() => getIsSubmitted());
const value = ref<unknown>(hasProvidedValue.value ? providedValue.value : defaultValue.value);
const pristine = ref<boolean>(getFieldPristine(name.value));
const dirty = computed(() => !isEqual.value(value.value, defaultValue.value));
const firstError = computed(() => errors.value[0]);
const invalid = computed(() => submitted.value && !!errors.value.length);
Expand Down Expand Up @@ -123,9 +126,8 @@ const field: Field = reactive({
}
});
value.value = hasProvidedValue.value ? providedValue.value : defaultValue.value;
const unregister = register(field);
onBeforeUnmount(() => {
onUnmounted(() => {
unregister();
});
registered.value = true;
Expand Down
2 changes: 2 additions & 0 deletions src/components/ValidationProvider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
getErrorsSymbol,
getFieldDefaultValueSymbol,
getFieldValueSymbol,
getFieldPristineSymbol,
getIsSubmittedSymbol,
hasFieldValueSymbol,
registerSymbol,
Expand Down Expand Up @@ -256,6 +257,7 @@ provide(validateSymbol, async (name: string) => {
});
provide(getFieldDefaultValueSymbol, getFieldDefaultValue);
provide(getFieldValueSymbol, (name: string) => get(values.value, name));
provide(getFieldPristineSymbol, (name: string) => fieldComponentMap.value[name]?.pristine ?? true);
provide(getErrorsSymbol, getErrors);
provide(hasFieldValueSymbol, (name: string) => has(values.value, name));
provide(getIsSubmittedSymbol, () => submitted.value);
Expand Down
3 changes: 3 additions & 0 deletions src/components/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export type GetFieldDefaultValue = (name: string, defaultValue?: unknown) => unk
export const getFieldDefaultValueSymbol: InjectionKey<GetFieldDefaultValue> =
Symbol('getFieldDefaultValue');

export type GetFieldPristine = (name: string) => boolean;
export const getFieldPristineSymbol: InjectionKey<GetFieldPristine> = Symbol('getFieldPristine');

export type GetErrors = (name?: string) => InnerValidationError[] | InnerValidationsErrors;
export const getErrorsSymbol: InjectionKey<GetErrors> = Symbol('getErrors');

Expand Down
23 changes: 23 additions & 0 deletions tests/unit/ValidationField.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,29 @@ describe('ValidationField', () => {
expect(fieldWrapper.emitted().change).toBeUndefined();
});

it('keep field state on component rerender', async () => {
createComponent();
await nextTick();

wrapper.findComponent(BaseInput);

expect(wrapper.findComponent({ ref: 'myInputValueSecond' }).props().modelValue).toBeUndefined();
expect(wrapper.findComponent({ ref: 'myInputValueSecond' }).props().pristine).toBe(true);
expect(wrapper.findComponent({ ref: 'myInputValueFirst' }).exists()).toBe(false);
wrapper.findComponent(BaseInput).vm.$emit('update:modelValue', 42);

await nextTick();
expect(wrapper.findComponent({ ref: 'myInputValueSecond' }).props().modelValue).toBe(42);
expect(wrapper.findComponent({ ref: 'myInputValueSecond' }).props().pristine).toBe(false);
expect(wrapper.findComponent({ ref: 'myInputValueFirst' }).exists()).toBe(false);

wrapper.findComponent({ ref: 'myNestedValueInput' }).vm.$emit('update:modelValue', 'test');
await nextTick();
expect(wrapper.findComponent({ ref: 'myInputValueFirst' }).props().modelValue).toBe(42);
expect(wrapper.findComponent({ ref: 'myInputValueFirst' }).props().pristine).toBe(false);
expect(wrapper.findComponent({ ref: 'myInputValueSecond' }).exists()).toBe(false);
});

it('should emit event on change', async () => {
createComponent({
props: {
Expand Down
29 changes: 27 additions & 2 deletions tests/unit/ValidationForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,33 @@
}"
>
<form @submit.prevent="handleSubmit">
<ValidationField name="my-input">
<ValidationField
v-if="$options.get(values, 'my.nested.value') === 'test'"
key="1"
name="my-input"
>
<template
#default="{ modelValue, name, firstError, errors, dirty, pristine, invalid, onChange }"
>
<base-input
ref="myInputValueFirst"
:name="name"
:first-error="firstError"
:errors="errors"
:model-value="modelValue"
:dirty="dirty"
:pristine="pristine"
:invalid="invalid"
@update:modelValue="onChange"
/>
</template>
</ValidationField>
<ValidationField v-else key="2" name="my-input">
<template
#default="{ modelValue, name, firstError, errors, dirty, pristine, invalid, onChange }"
>
<base-input
ref="myInputValueSecond"
:name="name"
:first-error="firstError"
:errors="errors"
Expand All @@ -41,6 +63,7 @@
#default="{ modelValue, name, firstError, errors, dirty, pristine, invalid, onChange }"
>
<base-input
ref="myNestedValueInput"
:name="name"
:first-error="firstError"
:errors="errors"
Expand Down Expand Up @@ -137,14 +160,16 @@ import {
ValidationProvider,
ValidationField,
ValidationFieldArray,
ValidationErrors
ValidationErrors,
get
} from '../../src';
import BaseInput from './BaseInput.vue';
import FormInfo from './FormInfo.vue';
import BaseErrors from './BaseErrors.vue';
export default {
name: 'ValidationForm',
get,
components: {
BaseErrors,
FormInfo,
Expand Down

0 comments on commit b442d1f

Please sign in to comment.