Vue 3 props property not updating - vue.js

I have parent component like below:
<template>
<button #click="initStr" value="init str" />
<child :str="str" />
</template>
<script>
export default {
components: { child, },
setup() {
const str= ref("");
function initStr() {
str.value = "init";
}
return {
str,
initStr,
};
}
};
</script>
The problem is when click the button on parent to init string, the child component does not rerender with the new string. I have to create another ref varible in child component then watch the props to assign the new string like below:
const string = ref(props.str);
watch(props, props => {
string.value = props.str;
});
Is this the only way to rerender the child when props from parent changes?

It should work as shown in the following example :
const {
createApp
} = Vue;
const App = {
setup() {
const str = Vue.ref("");
function initStr() {
str.value = "init";
}
return {
str,
initStr,
};
}
}
const app = createApp(App)
app.component('child', {
props: ['str'],
template: `
<div> str : {{str}}</div>
`
})
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app">
<button #click="initStr">
init str
</button>
<child :str="str" />
</div>

Related

Passing a value via prop, editing it and saving the new value vue 3

I am trying to pass a value into a child component. The child component will then preform the save operation. The parent doesn't need to know anything about it. I am able to pass in the object but not save its updated form.
Parent
<template>
<div v-show="isOpened">
<EditModal #toggle="closeModal" #update:todo="submitUpdate($event)"
:updatedText="editText" :todo="modalPost" />
</div>
</template>
<script setup lang="ts">
import Post from "../components/Post.vue";
import { api } from "../lib/api";
import { ref } from "vue";
import { onMounted } from "vue-demi";
import EditModal from "../components/EditModal.vue";
const postArr = ref('');
const message = ref('');
let isOpened = ref(false);
let modalPost = ref('');
let editText = ref('');
function closeModal() {
isOpened.value = false
}
function openModal(value: string) {
isOpened.value = true
modalPost.value = value
}
// call posts so the table loads updated item
function submitUpdate(value: any) {
console.log("called update in parent " + value)
editText.value = value
posts()
}
</script>
Child EditModal
<template>
<div>
<div>
<textarea id="updateTextArea" rows="10" :value="props.todo.post"></textarea>
</div>
<!-- Modal footer -->
<div>
<button data-modal-toggle="defaultModal" type="button"
#click="update(props.todo.blogId, props.todo.post)">Save</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { api } from "../lib/api";
import { reactive, ref } from "vue";
const props = defineProps({
todo: String,
updatedText: String,
})
const emit = defineEmits(
['toggle','update:todo']
);
function setIsOpened(value: boolean) {
emit('toggle', value);
}
function update(id: string, value: string) {
console.log('the value ' + value)
try {
api.updateBlog(id, value).then( res => {
emit('update:todo', value)
emit('toggle', false);
})
} catch (e) {
console.log('Error while updating post: '+ e)
}
}
</script>
I know the props are read only, therefore I tried to copy it I can only have one model.
I do not see the reason I should $emit to the parent and pass something to another variable to pass back to the child.
I am trying to pass in text to a modal component where it can edit the text and the child component saves it.
Advice?
First, your component should be named EditTodo no EditModal because you are not editing modal. All Edit components should rewrite props to new local variables like ref or reactive, so you can work on them, they won't be read only any more.
Child EditTodo.vue
<template>
<div>
<div>
<textarea id="updateTextArea" rows="10" v-model="data.todo.title"></textarea>
</div>
<div>
<button data-modal-toggle="defaultModal" type="button" #click="update()">Save</button>
</div>
</div>
</template>
<script setup lang="ts">
import { api } from "../lib/api";
import { reactive } from "vue";
const props = defineProps<{ id: number, todo: { title: string} }>()
const emit = defineEmits(['toggle']);
const data = reactive({ ...props })
// I assuming api.updateBlog will update data in database
// so job here should be done just need toogle false modal
// Your array with todos might be not updated but here is
// no place to do that. Well i dont know how your API works.
// Database i use will automaticly update arrays i updated objects
function update() {
try {
api.updateBlog(data.id, data.todo ).then(res => {
emit('toggle', false);
})
}
}
</script>
Parent
<template>
<div>
<BaseModal v-if="todo" :show="showModal">
<EditTodo :id="todo.id" :todo="todo.todo" #toggle="(value) => showModal = value"></EditTodo>
</BaseModal>
</div>
</template>
<script setup lang="ts">
const showModal = ref(false)
const todo = reactive({ id: 5, todo: { title: "Todo number 5"} })
</script>
I separated a modal object with edit form, so you can create more forms and use same modal. And here is a simple, not fully functional modal.
<template>
<div class="..."><slot></slot></div>
</template>
<script setup>
defineProps(['show'])
defineEmits(['toogle'])
</script>
You might want to close modal when user click somewhere outside of modal.

Extract modelValue logic to composable

I'm transitioning from Vue 2 to Vue 3 and I'm having trouble with composables.
I have a bunch of components that inherits modelValue. So, for every component that uses modelValue I'm writing this code (example with a radio input component):
<script setup>
import { computed } from 'vue'
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: {
type: [String, null],
required: true
}
})
const computedValue = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
</script>
<template>
<label class="radio">
<input
v-model="computedValue"
v-bind="$attrs"
type="radio"
>
<slot />
</label>
</template>
Is there a way to reuse the code for the modelValue?
I've just done this while I'm playing with Nuxt v3.
You can create a composable like this:
import { computed } from 'vue'
export function useModel(props, emit) {
return computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
}
<template>
<input type="text" v-model="value" />
</template>
<script setup lang="ts">
const props = defineProps({
modelValue: String,
})
const emit = defineEmits(['update:modelValue'])
const value = useModel(props, emit)
</script>
For completion of #BghinC's perfect answer here the fully typed version:
Composable
File: #/composables/useModelValue.ts
import {computed} from 'vue'
export default function useModelValue<T>(
props: {
modelValue: T
[key: string]: unknown
},
emit: (event: 'update:modelValue', ...args: unknown[]) => void
) {
return computed({
get: () => props.modelValue,
set: (value: T) => emit('update:modelValue', value),
})
}
Usage
<script setup lang="ts">
import useModelValue from '#/composables/useModelValue'
const props = defineProps<{
modelValue: Dog
}>()
const emit = defineEmits(['update:modelValue'])
const dog = useModelValue<Dog>(props, emit)
</script>

Is there a way to share reactive data between random components in Vue 3 Composition API?

Having some reactive const in "Component A," which may update after some user action, how could this data be imported into another component?
For example:
const MyComponent = {
import { computed, ref } from "vue";
setup() {
name: "Component A",
setup() {
const foo = ref(null);
const updateFoo = computed(() => foo.value = "bar");
return { foo }
}
}
}
Could the updated value of 'foo' be used in another Component without using provide/inject?
I am pretty new in the Vue ecosystem; kind apologies if this is something obvious that I am missing here.
One of the best things about composition API is that we can create reusable logic and use that all across the App. You create a composable functions in which you can create the logic and then import that into the components where you want to use it. Not only does this make your component much cleaner but also your APP much more maintainable. Below is a simple example of counter to show how they can be used. You can find working demo here:
Create a composable function for counter:
import { ref, computed } from "vue";
const counter = ref(0);
export const getCounter = () => {
const incrementCounter = () => counter.value++;
const decrementCounter = () => counter.value--;
const counterPositiveOrNegitive = computed(() =>
counter.value >= 0 ? " Positive" : "Negitive"
);
return {
counter,
incrementCounter,
decrementCounter,
counterPositiveOrNegitive
};
};
Then you can import this function into your components and get the function or you want to use. Component to increment counter.
<template>
<div class="hello">
<h1>Component To Increment Counter</h1>
<button #click="incrementCounter">Increment</button>
</div>
</template>
<script>
import { getCounter } from "../composables/counterExample";
export default {
name: "IncrementCounter",
setup() {
const { incrementCounter } = getCounter();
return { incrementCounter };
},
};
</script>
Component to decrement counter:
<template>
<div class="hello">
<h1>Component To Decrement Counter</h1>
<button #click="decrementCounter">Decrement</button>
</div>
</template>
<script>
import { getCounter } from "../composables/counterExample";
export default {
name: "DecrementCounter",
setup() {
const { decrementCounter } = getCounter();
return { decrementCounter };
},
};
</script>
Then in the main component, you can show the counter value.
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div class="counters">
<IncrementCounter />
<DecrementCounter />
</div>
<h3>Main Component </h3>
<p>Counter: {{ counter }}</p>
<p>{{ counterPositiveOrNegitive }}</p>
</template>
<script>
import IncrementCounter from "./components/IncrementCounter.vue";
import DecrementCounter from "./components/DecrementCounter.vue";
import { getCounter } from "./composables/counterExample";
export default {
name: "App",
components: {
IncrementCounter: IncrementCounter,
DecrementCounter: DecrementCounter,
},
setup() {
const { counter, counterPositiveOrNegitive } = getCounter();
return { counter, counterPositiveOrNegitive };
},
};
Hope this was somewhat helpful. You can find a working example here:
https://codesandbox.io/s/vue3-composition-api-blfpj

Vue 3 - template ref with computed?

How may I focus on this simple input example?
Should I create one more variable const nameRef = ref(null) or there is more beauty way to resolve this?
<template>
<input ref="name" :value="name" />
</template>
<script>
import {ref, computed} from 'vue';
export default {
props: ['name'],
setup(props) {
const name = computed(() => someTextPrepare(props.name));
// how can I do name.value.focus() for example?
return { name }
}
}
</script>
Try to wrap the name value and ref in reactive property :
<template>
<input :ref="theName.ref" :value="theName.value" />
</template>
<script>
import {ref, computed,reactive} from 'vue';
export default {
props: ['name'],
setup(props) {
const theName=reactive({
value:computed(() => someTextPrepare(props.name)),
ref: ref(null)
})
return { theName }
}
}
</script>

function outside vue, passing data into vue

I have a function that runs outside of a vue component. I want the data it returns passed to the data in the vue component.
<script>
function example(){
var item = 'item';
};
example();
export default {
data(){
return (this is where I want item represented)
}
}
Assign the function to a const and call it within one of the component lifecycle hooks:
const example = function (){
return 'item';
};
export default {
created () {
this.item = example()
},
data(){
return {
item: null
}
}
}
See Working Demo :
var app = new Vue({
el: '#app',
data: {
item: "Hi"
}
});
function example(){
app.item='Hello How R You ?';
};
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<button onclick="example()" >Click ME</button>
{{ item }}
</div>