Randy Hall
Randy Hall

Reputation: 8167

VueJS 3 - Passing prop to slot without reactive

I need to pass an object to a child slot, but that object will not be ready when the component is mounted and it absolutely cannot be a reactive because webpack will do bad things, even when it's a shallow reactive.

Short version: I got the google maps api (googlemaps/js-api-loader) all wired up with markers and clustered, and worked out all the bugs. Now I want to turn it into components and reuse it.

It was easy enough to wait for the map to be ready when it was all one component, but now I have markers in separate components (to allow easy layering/separate control of marker groups). Per the link above, I cannot make the map object reactive. However, the initial state is passed into children via properties, so it's always undefined.

Map component:

<template>
    <div class="map" ref="mapDiv"></div>
    <slot name="markers" :map="map" v-if="mapped"></slot>
</template>

Script setup():

const mapDiv = ref();
const mapped = ref(false);
let map;

new Loader(props.loader).load().then((result) => {
    map = new window.google.maps.Map(mapDiv.value, props.options);
    mapped.value = true;
    return result;
});

return {mapDiv, map, mapped}

I thought to add the v-if and see if it would grab the latest value, but that still uses the initial undefined value of map that was returned. Which makes total sense.

Massive workaround, I can shove the reference in the global window object and poll, but that breaks the Vue pattern. I also need a per-instance reference of the map object, as there may be multiple map components, which becomes a juggling nightmare.

The Question

Is there a way to pass a non-reactive property within the Vue component setup? Even a way to pass it as a delayed action?


Deeper explanation:

Vue wants us to do this:

const mapDiv = ref();
const map = reactive({});

new Loader(props.loader).load().then((result) => {
    map.assign(new window.google.maps.Map(mapDiv.value, props.options));
    return result;
});

return {mapDiv, map}

But the moment you shove the map in a reactive, Vue explodes in errors. The map object is not compatible with the reactive proxy. Even with a shallowReactive, it tries to traverse properties that point to cross-site resources from google, which is a security issue: the browser has a meltdown and it just doesn't work.

As an example, this works as expected:

const mapDiv = ref();
const map = reactive({}); //<-- WORKS BECAUSE REACTIVE

new Loader(props.loader).load().then((result) => {
    Object.assign(map, {test: 'Hello, world!'});
    return result;
});

return {mapDiv, map}

Upvotes: 0

Views: 1599

Answers (1)

Randy Hall
Randy Hall

Reputation: 8167

It's not necessary to use reactive for an object unless what your really need is reactivity. For basic awareness of the object change passed down to child components, a ref or shallowRef will do:

const map = ref({});
map.value = new window.google.maps.Map(mapDiv.value, props.options);

Upvotes: 1

Related Questions