Skip to content
This repository was archived by the owner on Apr 9, 2025. It is now read-only.

Commit 9b13060

Browse files
authored
Merge pull request #68 from dbssman/feature/66-add-error-message-component
🚨 Create error message component
2 parents a229f2a + af59d69 commit 9b13060

File tree

6 files changed

+236
-1
lines changed

6 files changed

+236
-1
lines changed

docs/.vitepress/config.cts

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export default defineConfig({
110110
},
111111
{ text: `useFormContext`, link: '/api/use-form-context' },
112112
{ text: `FormHandler`, link: '/api/form-handler' },
113+
{ text: `ErrorMessage`, link: '/api/error-message' },
113114
],
114115
},
115116
],

docs/api/error-message.md

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# \<ErrorMessage/>
2+
3+
The `ErrorMessage` component is an utility Intended to be used right away, or wrapped for your customisation needs.
4+
5+
Make sure you use `ErrorMessage` along with at least one of this situations:
6+
7+
- With an existing handler instance in the current/previous levels of the component tree.
8+
- With a known formState
9+
10+
Depending on your situation you might want to use the component in one of these two ways.
11+
12+
## Explicit formState
13+
14+
```vue
15+
<template @submit.prevent="handleSubmit(successFn)">
16+
<form>
17+
<form @submit.prevent="handleSubmit(successFn)">
18+
<input v-bind="register('firstName', { required: true })" />
19+
<ErrorMessage name="firstName" :form-state="formState">
20+
</form>
21+
</form>
22+
</template>
23+
<script setup lang="ts">
24+
import { useFormHandler } from 'vue-form-handler'
25+
26+
const { handleSubmit, register, formState } = useFormHandler();
27+
28+
const successFn = (form: Record<string, any>) => {
29+
console.log({ form })
30+
}
31+
</script>
32+
```
33+
34+
## Injected formState
35+
36+
```vue
37+
<template @submit.prevent="handleSubmit(successFn)">
38+
<form>
39+
<form @submit.prevent="handleSubmit(successFn)">
40+
<input v-bind="register('firstName', { required: true })" />
41+
<ErrorMessage name="firstName">
42+
</form>
43+
</form>
44+
</template>
45+
<script setup lang="ts">
46+
import { useFormHandler } from 'vue-form-handler'
47+
48+
const { handleSubmit, register } = useFormHandler();
49+
50+
const successFn = (form: Record<string, any>) => {
51+
console.log({ form })
52+
}
53+
</script>
54+
```
55+
56+
:::warning
57+
Since for this approach, the component uses `useFormContext` internally, remember to pass the custom injectionKey in case you might be using some for your handler. i.e:
58+
59+
```vue
60+
<template @submit.prevent="handleSubmit(successFn)">
61+
<form>
62+
<form @submit.prevent="handleSubmit(successFn)">
63+
<input v-bind="register('firstName', { required: true })" />
64+
<ErrorMessage name="firstName" :injection-key="injectionKey">
65+
</form>
66+
</form>
67+
</template>
68+
<script setup lang="ts">
69+
import { useFormHandler } from 'vue-form-handler'
70+
71+
const injectionKey = Symbol('test')
72+
73+
const { handleSubmit, register } = useFormHandler({
74+
injectionKey
75+
});
76+
77+
const successFn = (form: Record<string, any>) => {
78+
console.log({ form })
79+
}
80+
</script>
81+
```
82+
83+
:::

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ErrorMessage.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { defineComponent, h } from 'vue'
2+
import { useFormContext } from './useFormContext'
3+
import { PropType, computed } from '@vue/runtime-core'
4+
import { FormState, InjectionKey } from './types'
5+
import { defaultInjectionKey } from './constants'
6+
7+
export const ErrorMessage = defineComponent({
8+
name: 'ErrorMessage',
9+
props: {
10+
name: {
11+
type: String,
12+
required: true,
13+
},
14+
formState: {
15+
type: Object as PropType<FormState>,
16+
},
17+
injectionKey: {
18+
type: [String, Symbol] as PropType<InjectionKey>,
19+
default: defaultInjectionKey,
20+
},
21+
},
22+
setup(props) {
23+
const usableFormState = computed(() =>
24+
props.formState
25+
? props.formState
26+
: useFormContext(props.injectionKey).formState
27+
)
28+
return () => {
29+
const error = usableFormState.value.errors[props.name]
30+
return error ? h('span', error) : null
31+
}
32+
},
33+
})

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './FormHandler'
2+
export * from './ErrorMessage'
23
export * from './useFormHandler'
34
export * from './useFormContext'
45
export * from './types'

src/test/message.test.ts

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { defineComponent } from '@vue/runtime-core'
2+
import { ErrorMessage } from '../ErrorMessage'
3+
import { useFormHandler } from '../useFormHandler'
4+
import { mount } from '@vue/test-utils'
5+
import { expect, it, describe } from 'vitest'
6+
7+
describe('ErrorMessage', () => {
8+
it('should mount', () => {
9+
expect(ErrorMessage).toBeTruthy()
10+
11+
const { formState } = useFormHandler()
12+
const wrapper = mount(ErrorMessage, {
13+
props: {
14+
name: 'test',
15+
formState,
16+
},
17+
})
18+
expect(wrapper.exists()).toBeTruthy()
19+
})
20+
it('should render nothing when no error', () => {
21+
const { register, formState } = useFormHandler()
22+
register('field')
23+
const wrapper = mount(ErrorMessage, {
24+
props: {
25+
name: 'field',
26+
formState,
27+
},
28+
})
29+
expect(wrapper.html()).toBe('')
30+
})
31+
it('should render error when error', async () => {
32+
const { register, formState, setError } = useFormHandler()
33+
register('field')
34+
setError('field', 'some error')
35+
const wrapper = mount(ErrorMessage, {
36+
props: {
37+
name: 'field',
38+
formState,
39+
},
40+
})
41+
expect(wrapper.html()).toBe('<span>some error</span>')
42+
})
43+
it('should work with context', async () => {
44+
const TestComponent = defineComponent({
45+
template: `
46+
<ErrorMessage name="hello" />
47+
`,
48+
components: {
49+
ErrorMessage,
50+
},
51+
setup: () => {
52+
useFormHandler()
53+
},
54+
})
55+
const wrapper = mount(TestComponent)
56+
expect(wrapper.html()).toBe('')
57+
})
58+
it('should work with context and error', async () => {
59+
const TestComponent = defineComponent({
60+
template: `
61+
<ErrorMessage name="hello" />
62+
`,
63+
components: {
64+
ErrorMessage,
65+
},
66+
setup: () => {
67+
const { setError } = useFormHandler()
68+
setError('hello', 'some error')
69+
},
70+
})
71+
const wrapper = mount(TestComponent)
72+
expect(wrapper.html()).toBe('<span>some error</span>')
73+
})
74+
it('should work with injection key', async () => {
75+
const TestComponent = defineComponent({
76+
template: `
77+
<ErrorMessage name="hello" :injectionKey="injectionKey" />
78+
`,
79+
components: {
80+
ErrorMessage,
81+
},
82+
setup: () => {
83+
const injectionKey = Symbol('test')
84+
useFormHandler({
85+
injectionKey,
86+
})
87+
return {
88+
injectionKey,
89+
}
90+
},
91+
})
92+
const wrapper = mount(TestComponent)
93+
expect(wrapper.html()).toBe('')
94+
})
95+
it('should work with injection key and error', async () => {
96+
const TestComponent = defineComponent({
97+
template: `
98+
<ErrorMessage name="hello" :injectionKey="injectionKey" />
99+
`,
100+
components: {
101+
ErrorMessage,
102+
},
103+
setup: () => {
104+
const injectionKey = Symbol('test')
105+
const { setError } = useFormHandler({
106+
injectionKey,
107+
})
108+
setError('hello', 'some error')
109+
return {
110+
injectionKey,
111+
}
112+
},
113+
})
114+
const wrapper = mount(TestComponent)
115+
expect(wrapper.html()).toBe('<span>some error</span>')
116+
})
117+
})

0 commit comments

Comments
 (0)