-
-
Save lmiller1990/48c4fd841b67eaa4f226011f70351daf to your computer and use it in GitHub Desktop.
<template> | |
<v-input | |
v-slot="{ error }" | |
:min="5" | |
:max="10" | |
:value="username" | |
:validation-algorthm | |
> | |
<input v-model="username" /> | |
<div v-if="error" class="error"> | |
{{ error }} | |
</div> | |
</v-input> | |
</template> | |
<script> | |
import { ref } from 'vue' | |
import VInput from './v-input.vue' | |
export default { | |
components: { VInput }, | |
setup() { | |
return { | |
username: ref('') | |
} | |
} | |
} | |
</script> | |
<style> | |
.error { color: red; } | |
</style> |
<script> | |
function getError(value, { min, max }) { | |
if (!value) { | |
return 'Required' | |
} | |
if (value.length < min) { | |
return `Min is ${min}` | |
} | |
if (value.length > max) { | |
return `Max is ${max}` | |
} | |
} | |
import { computed } from 'vue' | |
export default { | |
props: ['min', 'max', 'value'], | |
setup(props, ctx) { | |
const error = computed(() => getError( | |
props.value, | |
{ min: props.min, max: props.max } | |
)) | |
return () => ctx.slots.default({ | |
error: error.value | |
}) | |
} | |
} | |
</script> |
I put this in a codesandbox here: https://codesandbox.io/s/lmiller1990-renderless-component-jxuw9?module=%2Fsrc%2FApp.vue
Nice idea, easy to try it out. I should do this for future blog posts.
I really don't see the point of this pattern. What's the use of a component if it only abstracts logic ? Why not just use a composition function ?
validateInput.js
import { computed } from 'vue'
const useValidateInput = (value, min, max) => {
const error = computed(() => {
if (!value) {
return 'Required'
}
if (value.length < min) {
return `Min is ${min}`
}
if (value.length > max) {
return `Max is ${max}`
}
}
return { error }
}
export default useValidateInput
Am I missing something ?
Interesting question @bviala. I think it's more or less the same thing - reusability via composables vs reusability via components. This pattern is probably less useful with the introduction of composables, as opposed to Vue 2 which did not have such a concept.
Perhaps a better example would be something like a multiselect component. You want to support:
- disabling items
- select multiple items
- de-select an item once selected
A simple implementation might be like this:
<template>
<multiselect
v-model="selectedItems"
:disabled-items="disabledItems"
:close-on-select="true"
:items="allItems"
/>
</template>
<script>
import { ref, onMount } from 'vue'
export default {
setup() {
const allItems = ref([])
const selectedItem = ref([])
onMount(async () => {
const response = await axios.get('/api/items')
allItems.value = response.data
})
return {
disabledItems: ['foo'],
allItems,
selectedItems
}
}
}
</script>
You also might want to allow the developer to fully customize the markup and appearance of the dropdown and options. This is the problem renderless components solve. It would look something like this:
<template>
<multiselect
v-model="selectedItems"
v-slot="{ toggleItem }"
:disabled-items="disabledItems"
:close-on-select="true"
:items="allItems"
>
<item
v-for="item in allItems"
:item="item"
@click="toggleItem"
/>
</multiselect>
</template>
<script>
// same as above
</script>
This is quite a bit more awkward to solve using a composable. How would you handle this? I didn't think about it too much in depth, there might be some nice way to do it with a composable, but this is the general problem renderless components solved, at least in Vue 2 before we had composables - customized markup while abstracting the implementation details.
I'd be interested in seeing a composable useMultiselect
implementation. It's possible renderless components are not as useful now that we have composables. What do you think?
I put this in a codesandbox here: https://codesandbox.io/s/lmiller1990-renderless-component-jxuw9?module=%2Fsrc%2FApp.vue