5

I have a renderless component in JavaScript that I am trying to convert to TypeScript. I'm running into errors declaring the render function in a Vue.extend-ed component:

(method) ComponentOptions<Vue, unknown, unknown, unknown, never[], Record<never, any>>.render?(createElement: CreateElement, hack: RenderContext<Record<never, any>>): VNode
  No overload matches this call.
  The last overload gave the following error.
  Argument of type '{ render(): void; }' is not assignable to parameter of type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<string, any>>'.
    Types of property 'render' are incompatible.
      Type '() => void' is not assignable to type '(createElement: CreateElement, hack: RenderContext<Record<string, any>>) => VNode'.
        Type 'void' is not assignable to type 'VNode'.ts(2769)
        vue.d.ts(90, 3): The last overload is declared here.

This is an example of what I am trying to do in TypeScript:

import Vue from 'vue'

export default Vue.extend({
  render() { // error happens when I add this. I tried typing the render with :VNode
             // and returning this.$scopedSlots.default({}), but error still occurs
  }
})

How do I fix this?

tony19
  • 125,647
  • 18
  • 229
  • 307
Joseph Izang
  • 55
  • 1
  • 4
  • What do you mean by - send props back to a parent component? Can you share original code snippet? – Harshal Patil Sep 19 '19 at 02:52
  • https://learn-vuejs.github.io/vue-patterns/patterns/#renderless-component this is what a renderless component is and this is what I want to achieve only with typescript – Joseph Izang Sep 19 '19 at 08:41

1 Answers1

8

render() has this signature:

render?(createElement: CreateElement, hack: RenderContext<Props>): VNode;

Notes for render() declaration:

  • hack does not need to be declared in your code
  • argument declarations are unnecessary in a renderless function
  • return type is VNode (i.e., a single root node), but Vue actually accepts VNode[] as a return (which is what this.$scopedSlots.default() returns)

Solution 1: Specify the return type as VNode, and return a single node that wraps this.$scopedSlots.default():

import Vue, { VNode, CreateElement } from 'vue'

export default Vue.extend({
  render(h: CreateElement): VNode {
    return h('div', this.$scopedSlots.default!({}))
  }
})

Solution 2: Use any type assertion on this.$scopedSlots.default() to workaround the type error:

import Vue from 'vue'

export default Vue.extend({
  render() {
    // The container node is expected to provide a single root,
    // so it's okay to return `VNode[]` as any.
    return this.$scopedSlots.default!({}) as any
  }
})
tony19
  • 125,647
  • 18
  • 229
  • 307
  • solution 2 worked for me because the component is renderless. Thank you @tony19. This isn't available to know in the docs so thank you for sharing your knowledge. – Joseph Izang Sep 22 '19 at 21:17
  • I've got error using second solution, but using normal slot works fine: `render() { return this.$slots.default! as any }` – Przemysław Melnarowicz Jun 01 '20 at 11:11
  • Yeah, above solution worked as long as I did not use mixin (defined unfortunetly as `any`). Adding mixin resulted in ts errors on every component property. Now I'm using `render(h) { return h('template') } and seems to work perfectly fine. – Przemysław Melnarowicz Jun 23 '20 at 15:30