Render dynamic component depending on dates in Vue.js

Contents

With Vue.js it is unbelievable easy to render dynamic components, which we can utilize to render content with specific components and layouts by only using their name.

We’ve already covered the topic on how to render component dynamically from a JSON, now we will focus on how to only render components if in the correct time frame.

The JSON structure

The content JSON has been enhanced by a start and end field for one of our two components. The start and end property in our example are not required and can result in an empty string. Each DateTime string is in GMT+0 just so we get the question about timezones out of our mind for this.

...
  data() {
    return {
      content: {
        body: [
          {
            _uid: "BUY6Drn9e1",
            component: "foo",
            headline: "Foo"
+            start: "2019-09-02 01:59",
+            end: "2019-09-14 20:56"
          },
          {
            _uid: "gJZoSLkfZV",
            component: "bar",
            title: "Bar"
          },
          {
            _uid: "X1JAfdsZxy",
            component: "foo",
            headline: "Another headline",
+            start: "2019-12-25 19:55",
+            end: ""
          }
        ]
      }
    }
  },
...

Filtering before or during looping through components?

We now have two possibilities on how to receive the components we want to actually how. In this small example filtering the components beforehand will be much easier by using a computed property, however with a nested and more complex content structure that might already be way harder as you no longer loop through one array of objects but a tree content structure instead.

The filter function using a computed property

Filtering dates in JavaScript is always a bit tricky, packages like momentjs make things easier. If you’re performing more complex date operations it’s good to know they exist, for this example we’ll use the default Date of JavaScript and add the timezone +0000 before using the date strings.

computed: {
  componentsToShow() {
    return this.content.body.filter(comp => {
      if (comp.start && comp.end) {
        let start = new Date(`${comp.start}+0000`);
        let end = new Date(`${comp.end}+0000`);
        return start <= new Date() && new Date() <= end;
      }

      if (comp.start) {
        let start = new Date(`${comp.start}+0000`);
        return start <= new Date();
      }

      if (comp.end) {
        let end = new Date(`${comp.end}+0000`);
        return new Date() <= end;
      }
      return true;
    });
  }
}

Instead of using the content.body property directly we will use our computed property componentsToShow.

- <template v-for="block in content.body">
+ <template v-for="block in componentsToShow"> 
  <component :is="block.component" :block="block" :key="block._uid"></component>
</template>

Using a method to determine if a component should be included

To use a method and the Vue.js template instead of using a computed property we extract the above filter method into a helper method, let us call it isComponentVisible.

methods: {
  isComponentVisible: comp => {
    if (comp.start && comp.end) {
      let start = new Date(`${comp.start}+0000`);
      let end = new Date(`${comp.end}+0000`);
      return start <= new Date() && new Date() <= end;
    }

    if (comp.start) {
      let start = new Date(`${comp.start}+0000`);
      return start <= new Date();
    }

    if (comp.end) {
      let end = new Date(`${comp.end}+0000`);
      return new Date() <= end;
    }
    return true;
  }
},

Since we’re not going to use the computed property we can return to our direct content structure using content.body.

-<template v-for="block in componentsToShow"> 
+<template v-for="block in content.body">
  <component :is="block.component" :block="block" :key="block._uid"></component>
</template>

The last step to filter our components is to use the isComponentVisible method with the current block object.

<template v-for="block in content.body">
-  <component :is="block.component" :block="block" :key="block._uid"></component>
+  <component v-if="isComponentVisible(block)" :is="block.component" :block="block" :key="block._uid"></component>
</template>

When should I use which approach?

We would recommend opting for the method approach as you can outsource that one method from your component and re-use it everywhere (where component might be used) in your Vue.js app. This way you no longer have the complexity of filtering out the components of the structure before rendering, but decide during rendering what to show.

Keep in mind that even tho the components are not displayed we would recommend to only use this approach with non-sensitive information. Your data is filtered on the client and the original information is still available through the browsers dev tools.

If you want to filter information before rendering on the client you would be able to use a server-side rendered website or a static site generator. Another option would be to go for a cloud function / custom API (Netlify Function / Zeit) to filter your component on a server.

Editing the JSON

Start End Schema in components of Storyblok

Adding a start and end field to your components can be done by pressing Define Schema in that component and adding two new fields. Select the type Date/Time and you’re ready to go. Try it out by exchanging the static content with our demo API Call right here.

Codesandbox example


More to read...

About the author

Dominik Angerer

Dominik Angerer

A web performance specialist and perfectionist. After working for big agencies as a full stack developer he founded Storyblok. He is also an active contributor to the open source community and one of the organizers of Stahlstadt.js.