javascriptphplaravelnuxt.js

How to make nuxt multiselect work properly with data from Laravel API?


I’m using the USelectMenu component from the Nuxt UI library, and I need to ensure that all the options that the user can select are displayed. I have that covered with my mapped_looking_for method, and the options are bound correctly. Then, I need to display all the options that the user has already selected, which I also have working to some extent.

The problem is that the select should work in such a way that if an option is selected, there should be a tick next to it on the right indicating that it is indeed selected. However, even though it shows that 5 options are selected, I don't see the tick next to them. I am also unable to interact with them in such a way that I could remove options. Essentially, it doesn't work for me; I would have to manually delete them from the database.

Could anyone offer some advice? I’m relatively new to working with APIs, and I might be passing the data incorrectly. Thank you.

This is frontend.

<template>
    <div class="">
        <Heading as="h2">
            <span>Your profile</span>
        </Heading>
        <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
            <form @submit.prevent="updateProfile">

                <Heading>
                    <UAvatar :src="getAvatarUrl(profile.avatar)" size="xl" />

                </Heading>

                <UFormGroup label="About me">

                    <UTextarea autoresize color="primary" placeholder="Search..." v-model="profile.bio" />
                </UFormGroup>

                <UFormGroup label="Name">
                    <UInput color="primary" disabled v-model="user.name" />
                </UFormGroup>

                <UDivider label="Stats" class="mt-5" />
                <UFormGroup label="Show age">

                    <UToggle on-icon="i-heroicons-check-20-solid" off-icon="i-heroicons-x-mark-20-solid"
                        v-model="profile.show_age" />
                </UFormGroup>
                <UFormGroup label="Age">
                    <UInput color="primary" disabled v-model="profile.age" />
                </UFormGroup>
                <UFormGroup label="Height">
                    <USelect color="primary" v-model="profile.height" :options="height_option" />
                </UFormGroup>
                <UFormGroup label="Weight">
                    <USelect color="primary" v-model="profile.weight" :options="weight_option" />
                </UFormGroup>
                <UFormGroup label="Body Type">
                    <USelect color="primary" v-model="profile.body_type" :options="body_types" />
                </UFormGroup>
                <UFormGroup label="Relationship Status">
                    <USelect color="primary" v-model="profile.relationship_status" :options="relationship_status" />
                </UFormGroup>
                <UDivider label="What are you looking for?" class="mt-5" />

                <USelectMenu v-model="selected_looking_for" :options="mapped_looking_for" multiple
    placeholder="Vyber, co hledáš"  value-field="value" label-field="label" 
    />
                <div class="selected-options">
                    <UBadge v-for="item in profile.looking_fors" :key="item.id" :label="item.name" color="primary"
                        class="mr-2 mb-2 mt-2" />
                </div>
                <UDivider label="Identity" class="mt-5" />
                <UFormGroup label="Gender">
                    <USelect color="primary" v-model="profile.gender" :options="genders" />
                </UFormGroup>
                <div class="flex justify-center mt-4">
                    <UButton color="primary" size="md" type="submit">
                        Save
                    </UButton>
                </div>
            </form>


        </div>
    </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const { api } = useAxios();
const profile = ref({});

const user = ref({});

const mapped_looking_for = ref([]);
const selected_looking_for = ref([]);
const genders = ['male', 'female']

const height_option = ref([{ label: 'Do not show', value: 'Do not show' }]);

const weight_option = ref([{ label: 'Do not show', value: 'Do not show' }]);

const relationship_status = ["Do not show", "Commited", "Dating", "Engaged", "Exclusive", "Married", "Open Relationship", "Partnered", "Single"]

const body_types = ["Do not show", "Toned", "Average", "Large", "Muscular", "Slim", "Stocky"]

const fetchLookingFor = async () => {
    try {
        const { data } = await api.get('/api/lookingFor');
        mapped_looking_for.value = data.data.map(item => ({
            label: item.name,
            value: item.id
        }));
        console.log('Mapped looking_for:', mapped_looking_for.value);
    } catch (error) {
        console.error('Error fetching looking for options:', error);
    }
};




const fetchProfile = async () => {
    try {
        const { data } = await api.get('/api/profiles/show');
        profile.value = data.profile;
        user.value = data.user;
        selected_looking_for.value = data.looking_fors.map(item => item.id);
    } catch (error) {
        console.error('Error fetching profile:', error);
    }
};


// Updating profile
const updateProfile = async () => {
    try {
        const looking_for_id = selected_looking_for.value.map(item => item.value);

        await api.put(`/api/profiles/${profile.value.id}`, {
            bio: profile.value.bio,
            gender: profile.value.gender,
            show_age: profile.value.show_age,
            height: profile.value.height,
            weight: profile.value.weight,
            body_type: profile.value.body_type,
            relationship_status: profile.value.relationship_status
        });

        await api.put(`/api/profiles/${profile.value.id}/looking-for`, {
            looking_for: looking_for_id
        });
        console.log('Profile updated successfully');
    } catch (error) {
        console.error('Error updating profile:', error);
    }
};


const getAvatarUrl = (avatarPath) => {
    const baseUrl = 'http://localhost:8080';
    return `${baseUrl}/storage/${avatarPath}`;
};

watch(() => profile.value.show_age, async (new_value) => {
    try {
        await api.put(`/api/profiles/${profile.value.id}`, {
            show_age: new_value
        });
        console.log('Show age updated successfully');
    } catch (error) {
        console.error('Error updating profile:', error);
    }
});


// height select

for (let i = 120; i <= 230; i++) {
    height_option.value.push({
        label: `${i} cm`,
        value: `${i}`
    });
}

// weight select

for (let x = 40; x <= 270; x++) {
    weight_option.value.push({
        label: `${x} kg`,
        value: `${x}`
    })
}

onMounted(() => {
    fetchProfile();
    fetchLookingFor();
});

</script>

This is what I have on backend.

 public function show(): JsonResponse
    {
        $user = Auth::user();

        $profile = $user->profile;

        return response()->json([
            'user' => [
                'name' => $user->name,
                'email' => $user->email,
            ],
            'profile' => $profile,
            'looking_fors' => $profile->lookingFors
        ]);
    }

 public function updateLookingFor(Request $request): JsonResponse
{
    $user = $request->user();
    $profile = $user->profile;
    $validated = $request->validate([
        'looking_for' => 'array',
        'looking_for.*' => 'exists:looking_fors,id',
    ]);

    $profile->lookingFors()->sync($validated['looking_for']);

    return response()->json(['message' => 'Looking for updated successfully']);
}

// And route

    Route::put('/profiles/{id}/looking-for', [ProfileController::class, 'updateLookingFor']);

Additional infos: This is the payload for the initial request when the user doesn't have anything saved in the looking_for field in the pivot table profiles_looking_fors:

{looking_for: [1, 2, 3]}

It stores the id of the respective looking_for, which is correct.

However, when I select the same values again (which essentially means I want to deselect them or remove them from the database to make it work in both directions), an error occurs:

"errors": {
    "looking_for.0": [
        "The selected looking_for.0 is invalid."
    ],
    "looking_for.1": [
        "The selected looking_for.1 is invalid."
    ],
    "looking_for.2": [
        "The selected looking_for.2 is invalid."
    ]
}

This is the payload: after errors

{looking_for: [null, null, null, 1, 2, 3]}

It sends null values along with 1, 2, 3.


Solution

  • I managed to solve the problem by creating my own multiselect component instead of using the one from the Nuxt UI library. The issue was with the Nuxt UI component itself, as I found on their GitHub issues page that it looks like it struggles to properly set the initial values when is user fetching data from backend API.