[Custom Components] - BUG with onMounted being fired twice after the new Update

Hello, this most likely will be a question for the WeWeb team. I have an issue, where the components I’ve created, that rely on the Vue’s onMounted() method fire twice. Let me explain.

Basically what happens, is that when I drop the element into the canvas, after the latest update, It appears to get created twice. Once for a brief moment, and then it disappears and once is a regular element, that’s being created. When I use ref() on this component, for example on a video, or a div, to pass to a script/work with later on, I get null, which causes often fatal issues with the program running.

So let’s say I have a video component like this:

<template>
  <div>
    <div class="brotilities-camera" :style="{ 'aspect-ratio': calculatedRatio }">
      <video :class="{ 'user-facing': isUserFacing }" ref="video" muted autoplay playsinline></video>
    </div>
  </div>
</template>

When I do an onMounted() like this (simplified for this example)

const video = ref(null);

// Gets the Media Stream
const getMediaStream = async (additionalConstraints = {}) => {

     console.log(video.value) // Logging

     // Set the src for the <videos>
     video.value.srcObject = stream; // Here it is manifested

     await new Promise(resolve => {
       video.value.onloadedmetadata = resolve;  // Here it is manifested
     });

};

onMounted(async () => {
     console.log(video.value) // Logging

     // Initiate the MediaStream and set the videos
      mediaStream.value = await getMediaStream();
})

Then I get the following logs.

I tried guarding this with an if(video.value), I also tried using nextTick() but nothing really helps. This is plaguing all of my components that use onMounted to work with refs, effectively costing me a huge amount of money and time without any way of fixing it. I’d like to request some help, because this is not normal Vue behavior (I have the same exact component in Vue and it works).

This wasn’t happening before the latest update, so I’m guessing it’s something new you guys introduced.

Maybe @Alexis or @Kevin you guys could help me shed some light on this? I’m planning to release the camera component, but I already spent like too much time on this, doing workarounds to make this work in WeWeb, but every time, a WeWeb particular thing stops me.

Don’t know if before the components where mounted twice, but your problem is the async callback in onMounted:
on the first render the callback is fired, but your async function takes more time that removing the element and mounting it again, so by the time your async function executes the first time the referenced element is not in the dom anymore

a guard inside the async function should prevent the error.

I actually tried that (adding a guard in both onMounted and the async func itself) but it still gives me the same result, because it’s mounted twice.

As I said, the Vue version of the component is working properly, so it’s most likely a WeWeb issue.
I’ve also reproduced it with a minimal setup out of the box of WeWeb’s elements base:

it may be a weweb issue that the component is mounted twice, but it’s your code that goes out of sync with the mounted element because its async logic.
where did you put the guard? it needs to be in your async function, not in the onmounted callback.

It’s at the beginning of the getMediaStream()

const getMediaStream = async (additionalConstraints = {}) => {
     if(video.value) {
      // Ideal constraints
      let idealConstraints = {
        width: { ideal: 4096 },
        height: { ideal: 2160 }
      }

      // If the device is mobile, make sure the startup facingMode is front camera
      // ...

      // Get the MediaStream
      const stream = await navigator.mediaDevices.getUserMedia({
        video: {
          ...idealConstraints,
          ...additionalConstraints
        },
        audio: false
      });

      console.log('inside getMediaStream', video.value)
      // Set the src for the <videos>
      video.value.srcObject = stream;

      await new Promise(resolve => {
        video.value.onloadedmetadata = resolve; // Changed to video
      });

      // Size the video properly
      resizeVideo();

      // Return the results and helping details

      if (props.content.debug) console.log("getMediaStream().result", result);
      return result;
    }
    };

The issue is that it gets through this guard and then it fails at the srcObject… because where it dies is unpredictable, due to WeWeb yanking that component away. I mean, the issue is not even the error, ofc, it’s there and I can guard for it via guarding before setting the srcObject, but the issue is, that then I lose all of the remaining context, such as I can’t remove the video tracks, because the component’s lifecycle doesn’t work properly (the first component is simply erroneous). This is not a discussion of how my code works I’m afraid, but of WeWeb being broken as usually - I shouldn’t have to deal with this in the first place.

It’s funny, because I have to resort to literally have if statements everywhere to check whether the element still exists. So I can’t even make one guard, but I have to guard for every statement that uses video, in the moment I do something like this:

It simply starts throwing the error within the guard. This not only adds guards here, but for example to whole other parts of my component, that wouldn’t even be affected by this piece of code, if I didn’t have to deal with this. It’s like it polluted my code with guards, because I’m working around a bug.

Your code should be resilient to mounting and unmounting any arbitrary amount of time. If you have side effects that need to be cleaned up you can use onBeforeUnmount, or you can also move your logic to an immediate watcher that uses the cleanup callback.

Yeah, I’m using that now, but still, having to work around these type of things is just poor DX, lets speak facts here.

you sound like people complaining about react strict mode :upside_down_face:

Man it should be pleasure and sexy codebase, not ugly code and struggles. That’s why we love Vue, right?

side effects require cleanup regardless of the framework.
your user can mount/unmount the component multiple times anyway

I still think that even with all the handling, it shouldn’t render twice, because it’s just pure terrible design.

I’d like to open this issue again, maybe even getting some feedback from the WeWeb devs, such as @Kevin, or anyone really. This tedious problem makes it impossible to even use some features such as the Teleports in WeWeb, whereas with Vue, this, of cource, works.

I’m attaching a super stupid and simple example, that just works in vanilla Vue, but probably due to this doesn’t work in WeWeb.

<template>
  <div id="test"></div>
  <Teleport to="#test">
    <p>Testing paragraph.</p>
  </Teleport>
</template>

<script setup>
import { Teleport } from 'vue';
</script>

This produces solid results in a simple Vue app, whereas in WeWeb, I’ve spent 3 hours trying to make it work, with nextTick, and other workarounds. To no avail. This and the fact that no-one from WeWeb answered this topic even with a simple explaination of the behavior makes me wanna stop all of my efforts to even produce anything in this space, because it just feels like a waste of time and energy. It’s been almost a month…

So @Raphael, you asked me about what you guys could do. I understand this is no usual issue. Yet answering the creators, even with things like this would be a nice start. Because what I don’t understand is why this has not recieved any attention for almost a month.

2 Likes

Hello, @Broberto

Have you created a ticket? https://support.weweb.io/ You can create a ticket and you will get an answer. Many users do this. For bugs like these, it’s better to create a ticket, not community.

Tickets regarding the issues that don’t include reporting that your app has imploded seems to be not important and scheduled for later time. So I’m guessing getting ignored here is the same as getting a confirmation of being ignored via e-mail, without having to deal with your terrible support app. Thanks for the suggestion though :slight_smile:

1 Like

Thanks for creating the ticket. The developers will answer based on their workload.

few months ago I created a private component that uses a list of Teleport, so it can be used in weweb.
I used references to actual DOM nodes for the to attribute instead of a css selector.

1 Like

I unfortunately don’t have that luxury, but a nice point indeed. The DOM element is not a Vue Element, it’s a modal generated by a custom Google Map Overlay, so no refs there. I could try to target it with the querySelectors, but I’m guessing that would do the same end. I’ll try it, but I’m so tilted by this that I’ll probably have to take a break, because this has just overall been a shitty experience : - ) I appreciate the input tho.

my DOM elements are not vue elements or anything that is under the component’s control, they are 100% managed by a third party library. I keep them in sync in a ref with callbacks exposed by the library. if you don’t have something like that I suppose you can try with an observer.

I’ll have a look, but this input gives me hope. Thank you man. It’s actually sad and funny at the same time, that you reply with better and more frequent answers than the WeWeb team Mariano. When it comes to these things, it’s such a tragedy to be a WeWeb user.