user3699170
user3699170

Reputation: 124

Vue 3 render function no class reactivity

I need to use hyperscript from vue (h function) but I have a problem with conditional class.

Here a reproduction link https://codesandbox.io/p/sandbox/quizzical-albattani-hxk5dd?file=%2Fsrc%2Fcomponents%2Finput.vue%3A14%2C20

in the input.vue view I display the props invalid (next to the input) and in the input itself I declare conditional class

:class="{ '--invalid': invalid }"

If I start writing in the input the props displayed change as expected, but the conditional class is not updated.

Any idea on how to correct this ?

Here my input component

<template>
  <div>
    invalid: {{ invalid }}
    <input
      class="theinput"
      :class="{ '--invalid': invalid }"
      @input="updateValue"
      v-model="value"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const props = defineProps(["invalid"]);
const value = ref("");
const emit = defineEmits(["update:value"]);

const updateValue = (event: Event) => {
  emit("update:value", (event.target as HTMLInputElement).value);
};
</script>

<style>
.theinput:focus {
  outline: none;
}
.--invalid {
  border: 1px solid red;
}
</style>

Here how can I call it with render function:

import { h, defineEmits } from "vue";

import theinput from "./input.vue";

const render = (details: any, invalid: any) => {
  const emit = defineEmits(["update:value"]);
  let copy = { ...details };
  copy.invalid = invalid;
  return h(theinput, {
    ...copy,
    "update:value": (value: any) => emit("update:value", value),
  });
};

export { render };

And this is app.vue:

<template>
  <!-- <theinput :invalid="invalid" @update:value="validate" /> -->
  <render-input @update:value="validate"></render-input>
</template>

<script setup>
import theinput from "./components/input.vue";
import { render } from "./components/input.ts";
import { ref, onBeforeMount } from "vue";

let invalid = ref(true);
const validate = (value) => {
  invalid.value = value.length < 3;
  console.log(invalid.value);
  console.log(renderInput.value);
};

onBeforeMount(() => {
  defineComponent();
});
let renderInput = ref(null);
const defineComponent = () => {
  renderInput.value = render({}, invalid);
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

enter image description here

When the invalid change to false the red border would disappear

EDIT

I fund the solution, in my component declaration

<div>
invalid: {{ invalid }}
<input
  class="theinput"
  :class="{ '--invalid': invalid }"
  @input="updateValue"
  v-model="value"
/>

the invalid: {{ invalid }} mustn't have .value but the :class="{ '--invalid': invalid }" need it. Both are in template so I don't know if it's a normal behaviour. I asked on the official git repo vue.

Upvotes: 0

Views: 135

Answers (1)

Asif Ali
Asif Ali

Reputation: 41

Using the : syntax for the class binding in the input.vue component:

<template>
  <div>
    invalid: {{ invalid }}
    <input
      class="theinput"
      :class="{ '--invalid': invalid }"
      @input="updateValue"
      v-model="value"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, watch, defineProps, defineEmits } from "vue";

const props = defineProps<{
  invalid: boolean;
}>();

const emit = defineEmits<{
  (e: "update:value", value: string): void;
}>();

const value = ref(props.invalid ? "" : null);

watch(
  () => props.invalid,
  (newValue) => {
    if (newValue) {
      value.value = "";
    }
  }
);

const updateValue = (event: Event) => {
  emit("update:value", (event.target as HTMLInputElement).value);
};
</script>

<style>
.theinput:focus {
  outline: none;
}

.--invalid {
  border: 1px solid red;
}
</style>

In this updated version, we are using the : syntax to bind the class attribute, and we are using the watch function to update the value reactive reference when the invalid prop changes.

Here's the updated render function in the input.ts file:

import { h, defineEmits, defineProps } from "vue";

import Theinput from "./input.vue";

const render = (value: any, invalid: any) => {
  const emit = defineEmits<{
    (e: "update:value", value: string): void;
  }>();

  let copy = { ...value };
  copy.invalid = invalid;
  return h(Theinput, {
    value: value,
    invalid: copy,
    "update:value": (v: string

Upvotes: -2

Related Questions