Adding condition to directive in Vue3 component

241 Views Asked by At

I am building a component which has two icons: like and dislike. When any of the icon is clicked, the icon should get bigger, show color and the upper heading should disappear.

I'm able to implement the functionality of hiding the heading when icon is clicked. The icon is coming from a shared Button component. I'm not able to comprehend how to give value to the bgColor based on condition that the icon is clicked on not.

For clarity:

  1. This is initial screen where icons aren't clicked yet: [![enter image description here][1]][1]

2.1. This is the screen which is appearing now when icon is clicked [Problem State] [![enter image description here][2]][2]

2.2: This is how the screen should appear when any icon is clicked [ Desired State]. [![enter image description here][3]][3]

My code for the component is:

    <template v-slot:main>
      <div class="feedbackComponent">
        <div class="feedback-content">
          <label v-if="!hideText" class="title">Appointment Confirmed!</label>
          <label class="subtitle">How would you rate your experience?</label>    
          <div>
            <ButtonIcon 
              symbol="thumbs-up" 
              @click="handleFeedback('up')"
              bgColor= "transparent; width: 70px; height: 70px; stroke-width: 1px"
            />
            <ButtonIcon 
              symbol="thumbs-down" 
              bgColor="transparent; width: 70px; height: 70px; stroke-width: 1px" 
              @click="handleFeedback('down')"
              
            />
          </div>
        </div>
      </div>
    </template>

This is script functions:

<script>
import ModalLayout from "@/components/Shared/ModalLayout.vue";
import ButtonIcon from '../Shared/ButtonIcon.vue';

export default {
  name: 'ScheduleTourFeedback',
  components: {
    ModalLayout,
    ButtonIcon,
  },

  data() {
    return {
      selected: null,
      hideText: false,
    }
  },

  methods: {
    handleFeedback(feedback) {
      this.selected = feedback
      this.$emit('feedback-clicked', true);
      this.handleClick();
    },

    handleClick() {
      this.hideText = true;
    },
  },
}
</script>

I want to implement conditional rendering on bgColor such that based on the icon click it changes to the value to

bgColor="transparent; width: 90px; height: 90px; stroke-width: 1px; fill: #d64ba1"

Sorry about the long post. I just wanted to make the use-case clear. Any help would be appreciated as I'm stuck on this for a while.

UPDATED: My ButtonIcon.vue component looks like this:

<template>
  <!---<button :disabled="disabled">{{ text }}</button>-->
  <a @click="onClick" :style="backgroundColor" :class="text ? 'TextIcon' : ''">
    {{ text }}
    <i
      :data-feather="symbol"
      :class="type ? 'btn-white' : 'btn-continue'"
      :style="backgroundColor"
    />
  </a>
</template>
<script>
import feather from 'feather-icons'
export default {
  name: 'ButtonIcon',
  props: {
    // iconCode: String,
    // iconColor: String,
    bgColor: String,
    symbol: String,
    disabled: Boolean,
    type: String,
    text: String,
  },
  computed: {
    backgroundColor() {
      let bgColor = this.bgColor ? this.bgColor : '#d64ba1'
      return 'background: ' + bgColor + ';' + 'border-radius: 24px;'
    }
  },
  mounted() {
    feather.replace()
  },
  data() {
    return {}
  },
  methods: {
    onClick() {
      this.$emit('onClick')
    },
  },
}
</script>
<style scoped>
.btn-continue {
  padding: 8px 10px;
  gap: 10px;
  width: 37px;
  height: 37px;
  position: relative;
  /* Quext/Pink */
  background: #d64ba1;
  border-radius: 24px;
  color: white;
  flex: none;
  order: 0;
  flex-grow: 0;
}
.btn-white {
  padding: 8px 10px;
  gap: 10px;
  width: 45px;
  height: 45px;
  position: relative;
  /* Quext/Transparent */
  background: white;
  border-radius: 24px;
  color: black;
  border: 2px solid #000;
  /* Inside auto layout */
  flex: none;
  order: 0;
  flex-grow: 0;
}
.TextIcon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 95px;
}
a {
  color: #fff;
}
</style>
1

There are 1 best solutions below

3
On

You can change the bgColor in response to the value of selected

For the thumbs-up icon, change

bgColor= "transparent; width: 70px; height: 70px; stroke-width: 1px"

to

:bgColor = "selected==='up'?
              'transparent; width: 90px; height: 90px; stroke-width: 1px; fill: #d64ba1':
              'transparent; width: 70px; height: 70px; stroke-width: 1px'"

And for the thumbs-down icon, do similarly but obviously make it test for selected==='down'.

Note that we need to put the : character before bgColor so that what is between the "" is no longer passed directly to ButtonIcon as a parameter, but rather it becomes interpreted as a Javascript expression, and the result is sent to ButtonIcon as a parameter.

How to debug

You comment that when you click on the icon it shows the problem state instead of the desired state.

There are three possible explanations.

  1. The this.selected is not being updated

  2. The bgColor is not being updated

  3. The bgColor is being updated, but the ButtonIcon component is not changing in appearance.

To separate these possibilities, between <div> and <ButtonIcon... add two pieces of information to assist you:

<div>this.selected is: {{selected}}</div>
<div>bgColor will be: {{
          selected==='up'?
              'transparent; width: 90px; height: 90px; stroke-width: 1px; fill: #d64ba1':
              'transparent; width: 70px; height: 70px; stroke-width: 1px'
          }}
</div>

Please report back what those messages say, before and after you click an icon.

Outcome was:

When button is unclicked, it said:

this.selected is: 
bgColor will be: 'transparent; width: 70px; height: 70px; stroke-width: 1px'

When clicked, it said:

this.selected is: up 
bgColor will be: 'transparent; width: 90px; height: 90px; stroke-width: 1px; fill: #d64ba1'

But the icon is not being updated.

Next step of debugging

There must be something wrong with the ButtonIcon component. Can you show that component please?

I am particularly interested in why a prop called bgColor should be doing so much more than setting the background colour.

Is it from a publically available library? If so please provide a link to the manual.

Is it constructed privately by you? If so, please provide a minimal version of the code.

OK the problem is the poorly named variables!

Change the name of the property to something like iconStyle because that is what it represents. bgColor was just one of the features it could transmit.

Then, in the ButtonIcon component:

Change

 <a @click="onClick" :style="backgroundColor" :class="text ? 'TextIcon' : ''">
 

to

 <a @click="onClick" :style="iconStyleEnhanced" :class="text ? 'TextIcon' : ''">

And in the JS, change

  props: {
    ...
    bgColor: String,
    ...
  },
  computed: {
    ...
    backgroundColor() {
      let bgColor = this.bgColor ? this.bgColor : '#d64ba1'
      return 'background: ' + bgColor + ';' + 'border-radius: 24px;'
    }
    ...
  }

to

  props: {
    ...
    iconStyle: String,
    ...
  },
  computed: {
    ...
    iconStyleEnhanced() {
      const bgStyle = "background: #d64ba1;";
      const radiusStyle = "border-radius: 24px;";
      return this.iconStyle +  bgStyle + borderStyle;
    }
    ...
  }

What was going wrong

The ButtonIcon component seems to be expecting bgColor to contain either a colour string or nothing (in which case a default colour was applied).

In practice you were stuffing multiple things into the bgColor prop, and prepending it with a background: string. This probably caused the CSS strings to become erroneous and therefore the change you caused was not creating the effect expected.

In the revised code, we are still stuffing multiple things into the prop, but now when we are in the child component it is obvious to us that the prop is not just a colour string. This helps us remember to treat it more carefully.