Skip to content

Instantly share code, notes, and snippets.

@lmiller1990
Created November 7, 2020 00:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save lmiller1990/48c4fd841b67eaa4f226011f70351daf to your computer and use it in GitHub Desktop.
Save lmiller1990/48c4fd841b67eaa4f226011f70351daf to your computer and use it in GitHub Desktop.
Renderless Components
<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>
@Glutnix
Copy link

Glutnix commented Nov 11, 2020

@lmiller1990
Copy link
Author

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.

@bviala
Copy link

bviala commented Nov 15, 2020

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 ?

@lmiller1990
Copy link
Author

lmiller1990 commented Nov 15, 2020

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment