How to implement two-way communication with props in a Vue.js component?

76 Views Asked by At

I have a Vue.js component in which I am using the syntax. I have a "name" props that I get from the parent component. I want to set up a two-way communication between parent and child components using this props.

Here's what the code of the parent component looks like:

<script setup>
import Greeting from './components/Greeting.vue'
import CreateTodo from './components/CreateTodo.vue'
import TodosList from './components/TodosList.vue'

import { ref, computed, watch, onMounted } from 'vue'

const name = ref('')

watch(name, newVal => {
    localStorage.setItem('name', newVal)
})
onMounted(() => {
    name.value = localStorage.getItem('name') || ''
})
</script>

<template>
    <main class="main">
        <Greeting class="greeting-section"/>
    </main>
</template>

And what my child component "Greeting.vue" looks like:

<script setup>
import MyInputName from './UI/MyInputName.vue'

defineProps({
    name: {
        type: String,
        required: true,
    },
})
</script>

<template>
    <section>
        <div class="container">
            <h2 class="title">
                What's up,
                <MyInputName placeholder="Name here" type="text"/>
            </h2>
        </div>
    </section>
</template>

It seems to me that it should be implemented using emit, but I do not quite understand how to implement it because instead of standard input I use custom UI component MyInput:

<template>
    <input
        :type="type"
        :name="name"
        :placeholder="placeholder"
        :value="modelValue"
        @input="$emit('update:modelValue', $event.target.value)"
    />
</template>

<script setup>
defineProps({
    modelValue: {
        modelValue: [String, Number],
    },
    name: {
        type: String,
    },
    type: {
        type: String,
        required: true,
    },
    placeholder: {
        type: String,
    }
})
defineEmits(['update:modelValue'])
</script>

<style scoped>
input[type="text"] {
    display: block;
    width: 100%;
    font-size: 1.125rem;
    padding: 1rem 1.5rem;
    color: var(--white);
    background-color: #FFF;
    border-radius: 0.5rem;
    box-shadow: var(--shadow);
    margin-bottom: 1.5rem;
    color: var(--dark);
}

input[type="radio"],
input[type="checkbox"] {
    display: none;
}

input[type="submit"] {
    display: block;
    width: 100%;
    font-size: 1.125rem;
    padding: 1rem 1.5rem;
    color: #FFF;
    background-color: var(--primary);
    border-radius: 0.5rem;
    box-shadow: var(--personal-glow);
    cursor: pointer;
    transition: 0.2s ease-in-out;
}
</style>

1

There are 1 best solutions below

1
On

In Greeting.vue component define an emit then use writable computed property that get the prop and when it's set it emits the value to parent :

<script setup>
import MyInputName from "./UI/MyInputName.vue";

defineProps({
  name: {
    type: String,
    required: true,
  },
});

defineEmits(["update:name"]);

const innername = computed({
  get() {
    return props.name;
  },
  set(value) {
    emit("update:name", value);
  },
});
</script>

<template>
  <section>
    <div class="container">
      <h2 class="title">
        What's up,
        <MyInputName placeholder="Name here" v-model="innername" type="text" />
      </h2>
    </div>
  </section>
</template>

In parent :

<Greeting class="greeting-section" v-model:name="name">