vue.jsvuejs2vue-componentvue-props

How to have component re-render after its props change in Vue 2?


My component is simple:

<template>
  <btn :color="color" @click="toggleColor">{{btnmsg}}</btn>
</template>

<script>
  import { Btn } from 'chico'
  export default {
    name: 'AButton',
    components: {
      Btn
    },
    data() {
      return {
        btnmsg: 'Legia pany',
        colors: [
          'blue', 'black', 'green', 
          'orange', 'teal', 'cyan', 
          'yellow', 'white'
        ],
        color: 'red'
      }
    },
    methods: {
      toggleColor() {
        this.color = this.colors[Math.floor(Math.random() * Math.floor(this.colors.length))];
      }
    }
  }
</script>

The <Btn> from the ChicoFamily goes something like this:

<template>
  <button :is="tag" :class="[className, {'active': active}]" :type="type" :role="role">
    <slot></slot>
  </button>
</template>

<script>
import classNames from 'classnames';
export default {
  props: {
    tag: {
      type: String,
      default: "button"
    },
    color: {
      type: String,
      default: "default"
    },
    //...it takes a lot of props...
  },
  data() {
    return {
      className: classNames(
        this.floating ? 'btn-floating' : 'btn',
        this.outline ? 'btn-outline-' + this.outline : this.flat ? 'btn-flat' : this.transparent ? '' : 'btn-' + this.color,
        //...classes derived from these props...
      ),
    };
  }
}
</script>

It's a button that, when clicked, should change its color. Clicking it indeed changes the prop passed, but does not re-render the button in the new color.

Why does passing a different prop not re-render the button?

The <Btn> takes its color from Bootstrap classes deriving from the prop. Could it be that the Btn gets the proper prop, but the className mechanic does not catch up?


Solution

  • This isn't a re-rendering problem– the component is indeed re-rendering when the prop changes. The problem is that the re-render does it no good, because you set the color as an initial value in data, and not as a computed property that gets re-evaluates when data changes.

    The way you did it, the className will be set once when the instance will is created, and never again.

    In order to make the className re-evaluate each time you change one of the props passed in, you will have to make a computed property out of it, like this:

    Btn component:

    export default {
      props: {
        [...]
      },
      computed: {
        className() {
          return classNames(
            this.floating ? 'btn-floating' : 'btn',
            this.outline ? 'btn-outline-' + this.outline : this.flat ? 'btn-flat' : this.transparent ? '' : 'btn-' + this.color
          );
        },
      },
    }