Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

AlexDFCH's avatar

How to put an array in useElementVisibility for building a "seen messages" by Vue.

Hi,

I'm building a simple chat box for support section of an online shop with Laravel, Vue.js 3 and Inertia.

I need when a new message seen by user, a request sent to server and set "seen" column at the "messages" table to true.

At first I did this, at the moment of loading page I assume user see every new message so I send a all-new-msgs-seen to server:

const postSeen = (msgid, userid) => {

    Inertia.post(route('messages.seen'),  {

        message_id: msgid,

        other_user: userid,

    });

};



onMounted(() => {

    postSeen('all', props.otherUser.id);

})

in MessageController:

public function seen(Request $request)
    {
        $validated = $request->validate([
            'message_id' => 'required',
            'other_user' => 'required',
        ]);

        if($validated['message_id'] == 'all'){
            Message::where('sender', $validated['other_user'])
                    ->where('receiver', Auth::id())
                    ->update(['seen' => true]);
        }else{
            $message = Message::find($validated['message_id']);

            $message->seen = true;

            $message->save();
        }
    }

It works correctly.

But it's a bad idea. Because when users are chatting and a new message loaded dynamically and seen by other user it doesn't send any request to server. I think I need recognize any message that get visible for user.

So I decide use useElementVisibility from vueUse library for this.

I want when a message get visible on the screen (so user seen the message) a request sent to server. When i using useElementVisibility for checking just one element visibility it's simple but my issue is how I should put an array of messages to useElementVisibility and when each of them get visible send the request to server?

const seenMessage = ref([]);

const skipUnwrap = { seenMessage } // This line is just because of a Vue bug.

let messageIsVisible = useElementVisibility(seenMessage); //This is wrong! What is right?


						<div v-for="message in messages">
                                <div v-if="message.sender !== $page.props.auth.user.id"
                                class="flex w-full mt-2 space-x-3 max-w-xs">
                                    <div class="flex-shrink-0 h-10 w-10 rounded-full bg-gray-300"></div>
                                    <div>
                                        <div :ref="skipUnwrap.seenMessage"
                                        class="bg-gray-300 p-3 rounded-r-lg rounded-bl-lg">
                                            <p class="text-sm">{{ message.message }}</p>
                                        </div>
                                        <span class="text-xs text-gray-500 leading-none">
                                            {{ formatedDateTime(message.created_at) }}
                                        </span>
                                    </div>
                                </div>
                                <div v-else> ...

I was thinking about it for a day and my brain is kinda locked and I don't have any idea for solving this problem.

FYI I'm new in front-end stuff, vue and js is too complicated to me yet!

I would be happy if you my friends give me a solution or a clue to know what I should to do.

Thank you.

0 likes
5 replies
LaryAI's avatar
Level 58

You can use the watch hook to watch the seenMessage array and check if any of the messages in the array are visible. If they are, you can then send a request to the server.

const seenMessage = ref([]);

const skipUnwrap = { seenMessage } // This line is just because of a Vue bug.

let messageIsVisible = useElementVisibility(seenMessage);

watch(seenMessage, (newVal, oldVal) => {
  newVal.forEach(message => {
    if (messageIsVisible[message.id]) {
      // Send request to server
      Inertia.post(route('messages.seen'),  {
        message_id: message.id,
        other_user: props.otherUser.id,
      });
    }
  });
});
AlexDFCH's avatar

@LaryAI Thank you Mr. AI.

But problem is not about Watch, problem is useElementVisibility don't accept an array.

AungHtetPaing__'s avatar

I had never used useElementVisibility so I don't know about it. But How about creating component for each message and use useElementVisibility in message component? That way you don't have to put array of messages to useElementVisibility.

AungHtetPaing__'s avatar
Level 22

@AlexDFCH I am not sure how to explain more about it. You can check demo code from vueUse and think of it demo as single message component and put your logic related with useElementVisibility to that component.

// move logic to message component
const seenMessage = ref([]);

const skipUnwrap = { seenMessage } // This line is just because of a Vue bug.

let messageIsVisible = useElementVisibility(seenMessage);


						<div v-for="message in messages">
                                <div v-if="message.sender !== $page.props.auth.user.id"
                                class="flex w-full mt-2 space-x-3 max-w-xs">
									<message-component :message="message"></message-component>
                                </div>
                                <div v-else> ...

In my case, I use echo with pusher and reload the component to mark the message as read. It works for two people chat.

<script setup>
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout.vue";
import DropdownLink from "@/Components/DropdownLink.vue";
import Messages from "./Partials/Messages.vue";
import Dropdown from "@/Components/Dropdown.vue";
import { Head, router, useForm, usePage } from "@inertiajs/vue3";
import { onMounted, onUnmounted, computed, reactive, ref } from "vue";

const props = defineProps({
    messages: Object,
    chatSession: Object,
    participants: Object,
});

// ...

function markAsRead() {
    router.reload();
}

// ....

onMounted(() => {
    window.Echo.join("chatsession." + props.chatSession.id)
        .here((users) => {
            activeParticipants.value = users;
        })
        .joining((user) => {
            activeParticipants.value.push({ name: user.name });
        })
        .leaving((user) => {
            activeParticipants.value.splice(
                activeParticipants.value.indexOf(user),
                1
            );
        })
        .listen("MessageSent", (e) => {
            messages.push(e.message);

            markAsRead();
        });
});

onUnmounted(() => {
    window.Echo.leave("chatsession." + props.chatSession.id);
});
</script>

// controller show
public function show(ChatSession $chatSession)
    {
        abort_if(!$chatSession->users->contains(Auth::id()), 404);

        $messages = Message::query()
            ->where('chat_session_id', $chatSession->id)
            ->addSelect([
                'nickname' => Participant::select('nickname')
                    ->whereColumn('user_id', 'messages.sender_id')
                    ->where('chat_session_id', $chatSession->id)
                    ->take(1)
            ])
            ->with('user')
            ->get();

        Message::query()
            ->where('chat_session_id', $chatSession->id)
            ->where('sender_id', '!=', Auth::id())
            ->update(['read_at' => now()]);

        return Inertia::render('Chat/Show', [
            'messages' => MessageResource::collection($messages),
            'chatSession' => ChatSessionResource::make($chatSession)->only('id', 'name', 'is_group'),
            'participants' => UserResource::collection($chatSession->users)
        ]);
    }
1 like

Please or to participate in this conversation.