Harri
Harri

Reputation: 131

How to pass a style from one component to another if condition is true in Vue 3

I have two sibling components where one relies on the other for an event to happen. In this case, I want to apply this styling - marginTop = "-20px"; to the div of one component when the length of an input inside another component is more than 0 characters.

Whether it be props, slots, refs or events, I'm still not sure what fits this use case (I'm very new to Vue). I'm also not sure if I can even pass data between these two sibling components directly or if I have to pass from one child to the parent and then to the other child.

What I'm currently attempting to do is to simply grab the element I want to apply the style to (in the vanilla JS way) and have the method invoked when the aforementioned condition becomes true (all in the same component).

// Component trying to style the element
<ais-state-results v-if="input.length > 0">
// Component trying to style the element
data() {
  return {
    input: ""
  }
}
mounted() {
      this.positionSecondaryContent();
},
methods: {
  positionSecondaryContent() {
  const secondaryContent = document.querySelector('.secondary-content');
  secondaryContent.style.marginTop = "-20px";
  }
},
// Component that has the element to be styled
<template>
  <div class="secondary-content">
    <TheWordOfTheDay />
    <SuggestTranslationForm />
  </div>
</template>

Aside from not knowing if I really should be communicating with other components, I'm not sure what to write in the ais-state-results component instance above to attach it to my method and then have the method run as soon as the condition is met.

Edit

I've included the property I want to check the length of in the second code box - (data() { return { input: "" } }

What I probably didn't make clear is that I want to dynamically style an element from one component <div class="secondary-content"></div> based on the input length of a property from another component input: "" but I don't how to link the two together.

Upvotes: 0

Views: 747

Answers (1)

tao
tao

Reputation: 90038

Vue is state driven.
In other words, you should only care about updating the controller/state and Vue will take care of updating DOM.

Here's how applying style conditionally typically looks like in Vue:

const { createApp, computed, reactive, toRefs } = Vue;
createApp({
  setup() {
    const state = reactive({
      someCondition: false,
      divStyle: computed(() =>
        state.someCondition ? { marginTop: "-20px" } : null
      ),
    });
    return { ...toRefs(state) };
  },
}).mount("#app");
.my-div {
  border: 1px solid red;
  transition: margin-top 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
body {
  padding: 2rem;
}
label {
  cursor: pointer;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js"></script>
<div id="app">
  <div :style="divStyle" class="my-div">This is a test</div>
  <label>
    <input type="checkbox" v-model="someCondition" />Apply negative margin
  </label>
  <pre v-text="{ someCondition, divStyle }" />
</div>

The computed divStyle updates based on current value of someCondition. When true, it returns the negative margin. When false, it returns null.

The main takeaway here is I'm not updating the view/DOM. Vue does it for me. All I do is change the values of the controller.


Update, after you've clarified the logic:

Considering this use-case, having the input in a child component seems an unnecessary complication. You should have the negative margin and the input in the same component.

But, let's say we're not talking about this particular case and we're talking about a case where you really need the child to update the parent.

There are multiple ways of sharing state across components:

  1. Probably the simplest would be an external reactive() object. Simply put, reactive objects are not restricted to usage inside components. They can be external and imported in multiple components.
    The only reason I don't recommend it is because a store is a far better option: easier to maintain, scale and debug.
    Note the above solution can also be wrapped in a useStuff() function which is the typical solution for sharing any functionality (including reactive state) across multiple components in Compostion API. Reactivity could also be provided by ref(), instead of reactive().
  2. [Parent/child]: emit an event from the child, which updates the parent. I don't recommend over-using it, especially multiple levels deep (prop drilling). Doesn't scale, hard to maintain.
  3. [Parent/child]: dual-binding . Quite similar to the above, has the same inconveniences.
  4. Last, but not least, you could use a store. I highly recommend pinia. The best option, by far, IMHO. At its core sits a reactive object (the state), but it has everything you could want from a state mgmt solution (seamless integration with dev tools for easy debugging, persist state to localstorage, getters, actions, subscriptions).

The above list is not exhaustive. You also have provide/inject, globalProps, just to name two more. You could also use event driven patterns (e.g: event bus, rxjs) but those are typically considered a depart from Vue's principles, although they can be used in Vue.

Upvotes: 1

Related Questions