Richard Schäfer
Richard Schäfer

Reputation: 11

Vue.js - recursive call of the component through render function. (runtime only)

I have one big request for you. I am a high-school student and I want to create an app for students with my friend. In the begining we wanted to use React for our reactive components, but then we saw Vue and it looked really good. But because of the fact, that we already have a big part of the app written in twig, we didn't want to use Vue.js standalone, because we would have to change a lot of our code, especially my friend, which is writing backend in Sympfony. So we use the runtime only version, which does not have a template option, so i have to write render functions for our components. And i am stucked with one particular problem.

I am writing a file-manager, and i need to render layer for every folder. Code is better then million words, so, take a look please :

var data = {
    name: 'My Tree',
    children: [
        {
            name: 'hello',
            isFolder: false,
        },
        {
            name: 'works',
            isFolder: true,
            children: [
              {
                  name: 'child2',
                  isFolder: true,
              },
              {
                  name: 'child3',
                  isFolder: false,
              },
            ]
        }

    ]
}


Vue.component('layer', {
  render: function renderChild (createElement) {

    if(data.children.length){
      return createElement('ul', data.children.map(function(child){
        return createElement('li', {
          'class' : {
            isFolder: child.isFolder,
            isFile: !child.isFolder
          },
          attrs: {
            id: "baa"
          },
          domProps: {
            innerHTML: child.name,
          },
          on:{
            click: function(){
              console.log("yes");
            },
            dblclick: function(){
              console.log("doubleclicked");
              if(child.children.length){
                  // if this has children array, create whole "layer" component again.

              }
            }
          }}
        )
      }))
    }
  },
  props: {
    level: {
      type: Number,
      required: true
    },
    name: {
      type: String,
    }
  }
})

new Vue({
  el: '#fileManagerContainer',
  data: data,

  render (h) {
    return (
      <layer level={1} name={"pseudo"}>
      </layer>
    )
  }
})

My question is, how to write that recursive call, which will render the whole Layer component on the doubleclick event, if the element has children array.

Thank you in advance for any reactions, suggestions or answers :)

Upvotes: 1

Views: 2673

Answers (1)

djeglin
djeglin

Reputation: 273

I know this is a very old question, so my answer won't be useful to the OP, but I wanted to drop in to answer because I found myself with the very same problem yesterday.

The answer to writing these recursive render functions is to not try to recurse the render function itself at all.

For my example, I had a set of structured text (ish) - An array of objects which represent content - which can be nested, like so:

[
  // each array item (object) maps to an html tag
  {
    tag: 'h3',
    classes: 'w-full md:w-4/5 lg:w-full xl:w-3/4 mx-auto font-black text-2xl lg:text-3xl',
    content: 'This is a paragraph of text'
  },
  {
    tag: 'img',
    classes: 'w-2/3 md:w-1/2 xl:w-2/5 block mx-auto mt-8',
    attrs: {
      src: `${process.env.GLOBAL_CSS_URI}imgsrc.svg`,
      alt: 'image'
    }
  },
  {
    tag: 'p',
    classes: 'mt-8 text-xl w-4/5 mx-auto',
    content: [
      {
        tag: 'strong',
        content: 'This is a nested <strong> tag'
      },
      {
        content: ' '
      },
      {
        tag: 'a',
        classes: 'underline ml-2',
        content: 'This is a link within a <p> tag',
        attrs: {
          href: '#'
        }
      },
      {
        content: '.'
      }
    ]
  }
]

Note the nested elements - These would need recursion to render properly.

The solution was to move the actual rendering work out to a method as follows:

export default {
  name: 'block',

  props: {
    block: {
      type: Object,
      required: true
    }
  },

  methods: {
    renderBlock (h, block) {
      // handle plain text without a tag
      if (!block.tag) return this._v(block.content || '')

      if (Array.isArray(block.content)) {
        return h(block.tag, { class: block.classes }, block.content.map(childBlock => this.renderBlock(h, childBlock)))
      }

      // return an html tag with classes attached and content inside
      return h(block.tag, { class: block.classes,  attrs: block.attrs, on: block.on }, block.content)
    }
  },

  render: function(h) {
    return this.renderBlock(h, this.block)
  }
}

So the render function calls the renderBlock method, and that renderBlock method is what calls itself over and over if it needs to - The recursion happens within the method call. You'll see that the method has a check to see whether the content property is of an Array type - At this point it performs the same render task, but rather than passing the content as-is, it passes it as an Array map, calling the same render method for each item in the array.

This means that, no matter how deeply the content is nested, it will keep calling itself until it has reached all the way to the "bottom" of the stack.

I hope this helps save someone some time in the future - I certainly wish I'd had an example like this yesterday - I would have saved myself a solid couple of hours!

Upvotes: 5

Related Questions