Nov 28, 2023
0
Level 1
Update a from element from a child using useForm
In my app I have several FilterMultiSelector components (in this example I simplified it) and I try to find I way to update my form when the child component emits changes to the parent.
This is my Parent Component:
<script setup>
import {useForm, usePage} from '@inertiajs/vue3'
import FilterMultiSelector from '@/Composables/FilterMultiSelector.vue'
const page = usePage()
defineProps({
assets: {
type: Array,
},
})
const form = useForm({
name: '',
assets: [],
})
const submit = () => {
form.post(route('store'), {
onFinish: () => {
form.name = ''
form.assets = []
},
})
}
const handleSelectedAssetsUpdate = (objects) => {
console.log(objects)
form.assets = [...objects]
}
</script>
<template>
<div class="my-2">
<FilterMultiSelector
:objects="assets"
name="Assets"
:filterAttributes="['label', 'property_name']"
@update:selectedObjects="handleSelectedAssetsUpdate"
:selected-objects="form.assets">
<template #displayFilter="{object}">
<div class="flex justify-between text-xs">
<span class="font-bold">{{ object.label }}</span>
<span class="">{{ object.property_name }}</span>
</div>
</template>
<template #displaySelection="{object}">
<div class="flex justify-between gap-2">
<span class="font-bold">{{ object.label }}</span>
<span>{{ object.property_name }}</span>
</div>
</template>
</FilterMultiSelector>
</div>
</template>
and this is the FilterMultiSelctor component, where basically the remove method needs attention:
<script setup>
import {computed, ref} from 'vue'
import useDetectOutsideClick from '@/Composables/useDetectOutsideClick.js'
import InputLabel from '@/Components/InputLabel.vue'
import TextInput from '@/Components/TextInput.vue'
import {XMarkIcon} from '@heroicons/vue/24/outline'
let {name, objects, filterAttributes, selectedObjects} = defineProps({
name: {
type: String,
},
objects: {
type: Array,
},
filterAttributes: {
type: Array,
default: () => [],
},
selectedObjects: {
type: Array,
default: () => [],
},
})
const emit = defineEmits(['update:selectedObjects'])
const componentRef = ref()
const inputRef = ref()
const searchString = ref('')
const filteredObjects = computed(() => {
return objects.filter((object) => {
const searchLower = searchString.value.toLowerCase()
return filterAttributes.length > 0
? filterAttributes.some((key) => {
const value = object[key]
return typeof value === 'string' && value.toLowerCase().includes(searchLower)
}) && !selectedObjects.includes(object)
: Object.keys(object).some((key) => {
const value = object[key]
return typeof value === 'string' && value.toLowerCase().includes(searchLower)
}) && !selectedObjects.includes(object)
})
})
const add = (object) => {
if (!selectedObjects.includes(object)) {
selectedObjects.push(object)
emit('update:selectedObjects', selectedObjects)
}
searchString.value = ''
inputRef.value.focus()
}
const remove = (object) => {
const newSelectedObjects = selectedObjects.filter((t) => t.id !== object.id)
emit('update:selectedObjects', [...newSelectedObjects])
}
useDetectOutsideClick(componentRef, () => {
searchString.value = ''
})
</script>
<template>
<div class="relative">
<InputLabel
for="object"
:value="name" />
<TextInput
ref="inputRef"
id="object"
type="text"
class="mt-1 block w-full"
v-model="searchString"
placeholder="Search object ..."
auotofocus />
<div
v-if="filteredObjects.length && searchString"
class="absolute left-[.5%] top-[60px] z-10 w-[99%] rounded border bg-white p-4 shadow"
ref="componentRef">
<div
v-for="object in filteredObjects.slice(0, 10)"
:key="object.id"
@click="add(object)"
class="rounded px-2 py-1 text-sm hover:cursor-pointer hover:bg-gray-100">
<slot
name="displayFilter"
:object="object" />
</div>
<div
v-if="filteredObjects.length > 10"
class="pl-2 text-xs tracking-widest">
...
</div>
</div>
<div
v-if="selectedObjects.length"
class="mt-2 flex flex-wrap gap-2">
<div
v-for="object in selectedObjects"
:key="object.id"
class="inline flex max-w-full items-center whitespace-nowrap rounded bg-primary-500 px-2 py-1 text-xs text-white hover:cursor-pointer hover:bg-primary-400"
@click="remove(object)">
<slot
name="displaySelection"
:object="object" />
<XMarkIcon class="ml-1 h-4 w-4" />
</div>
</div>
</div>
</template>
The data structure of assets is:
assets:Array
0: Reactive
id: 1
label:"Foo"
property_name:"Bar"
1 ...
When I now add three assets: Foo, Bar, Lorem and remove Lorem then I get Foo and Bar. But now, if I remove Bar, I get Foo and Lorem. And if I would now remove Foo, I'd get Bar and Lorem. So there is an issue in reactivity somewhere that I don't see.
Please or to participate in this conversation.