Leaflet error > ReferenceError: L is not defined - vue.js

One strange fact was that the first time I saved the code, the map appeared perfectly on the screen. But it was only this one time.
I don't know what is going on. The code looks perfect, and I don't know what this error in the console means.
I am unable to fix these errors:
runtime-core.esm-bundler.js:38 [Vue warn]: Unhandled error during execution of mounted hook
at <LeafletMap lat=-20.3612397 lng=-40.2958806 >
at <[id] onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< Proxy {__v_skip: true} > >
at <Anonymous key="/anuncio/kqnceP5qQ3QTl0d9wrFM" routeProps= {Component: {…}, route: {…}} pageKey="/anuncio/kqnceP5qQ3QTl0d9wrFM" ... >
at <BaseTransition mode="out-in" appear=false persisted=false ... >
at <Transition name="page" mode="out-in" >
at <RouterView name=undefined route=undefined >
at <NuxtPage>
at <Default >
at <Anonymous key="default" name="default" hasTransition=true >
at <BaseTransition mode="out-in" appear=false persisted=false ... >
at <Transition name="layout" mode="out-in" >
at <Anonymous>
at <App key=1 >
at <NuxtRoot>
and:
Uncaught (in promise) ReferenceError: L is not defined
at LeafletMap.vue:24:1
at runtime-core.esm-bundler.js:2723:40
at callWithErrorHandling (runtime-core.esm-bundler.js:155:22)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:164:21)
at hook.__weh.hook.__weh (runtime-core.esm-bundler.js:2697:29)
at flushPostFlushCbs (runtime-core.esm-bundler.js:341:32)
at flushJobs (runtime-core.esm-bundler.js:395:9)
My LeafletMap component:
<script setup>
const props = defineProps({
// props go here
lat: {
type: Number,
required: true
},
lng: {
type: Number,
required: true
}
})
// leaflet map 1.9.2
const map = ref(null)
const marker = ref(null)
const mapContainer = ref(null)
const mapOptions = {
center: [props.lat, props.lng],
zoom: 13,
zoomControl: false
}
// load map
onMounted(async () => {
map.value = await L.map(mapContainer.value, mapOptions)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution:
'© OpenStreetMap contributors'
}).addTo(map.value)
marker.value = await L.marker([props.lat, props.lng]).addTo(map.value)
})
// update marker position
watch(
() => props.lat,
() => {
marker.value.setLatLng([props.lat, props.lng])
map.value.setView([props.lat, props.lng], 13)
}
)
watch(
() => props.lng,
() => {
marker.value.setLatLng([props.lat, props.lng])
map.value.setView([props.lat, props.lng], 13)
}
)
// destroy map
onUnmounted(() => {
map.value.remove()
})
</script>
<template>
<div class="relative h-96 w-full" ref="mapContainer"></div>
</template>
<style scoped>
#import 'https://unpkg.com/leaflet#1.9.2/dist/leaflet.css';
</style>
The props are going to the component:
<LeafletMap :lat="ad.geolocation.lat" :lng="ad.geolocation.lng" />

OP solved the issue by importing Leaflet
const L = await import('leaflet')

Related

Can't get events from emits in my composable

I'm trying to generate a confirmation modal when calling my composable, my component instance is mounting well, but I can't access the emits via the : onCancel
The goal is to call the dialer every time I need to interact with a confirmation
useModalConfirm.ts
function confirm(props: ModalConfirmProps) {
const container = document.createElement('div');
document.body.appendChild(container);
const component = createVNode(ModalConfirm, {
...props,
// not working here :(
onCancel: () => {
console.log('canceled')
}
});
render(component, container);
return component.component;
}
ModalConfirm.vue
<script lang="ts" setup>
import {NButton} from "naive-ui";
const emits = defineEmits(["onConfirm", "onCancel"]);
export type ModalConfirmProps = {
title: string;
message: string;
confirmButtonText: string,
cancelButtonText: string,
};
const props = defineProps<ModalConfirmProps>();
const confirm = () => {
emits("onConfirm");
};
const cancel = () => {
emits("onCancel");
};
</script>
<template>
<div class="ModalConfirm">
<div class="ModalConfirmContent">
{{ props.title }}
{{ props.message }}
<NButton #click="cancel" type="error">{{ props.cancelButtonText }}</NButton>
<NButton #click="confirm" type="success">{{ props.confirmButtonText }}</NButton>
</div>
</div>
</template>
any ideas ?

Prop being passed but not updating the component

So I have a function called updateAll(data.items) which should take the array and randomly assign a new name, it appears the data.items is updating when I view in vue dev tools and is being passed to the component but not actually updating the browser
I've also added in my buildData.js this is being used for the same app but in react so was trying to keep it as general as I could
//HomeView.vue
<script setup>
import { reactive, watch } from "vue";
import Hero from '#/components/Hero.vue'
import FunctionButton from '#/components/FunctionButton.vue'
import ItemsContainer from '#/components/ItemContainer.vue';
import * as dataFunc from '#/data/buildData'
const data = reactive({
items: JSON.parse(localStorage.getItem('items')) || []
})
console.log(data.items)
watch(() => data.items,(newValue) => {
localStorage.setItem('items', JSON.stringify(newValue))
}, {deep: true});
</script>
<template>
<div class='w-2/3 mx-auto text-center'>
<Hero HeroText="The one stop shop to store recipes!" />
<main>
<h1 class="text-5xl font-extrabold dark:text-white">Recipe Manager</h1>
<div class='functions gap-4 grid grid-cols-2 md:grid-cols-3 mt-8 mx-auto w-fit'>
<FunctionButton name="Add new recipe" #click="() => data.items = [...data.items,...dataFunc.buildData(1)]"/>
<FunctionButton name="Add 100 recipes" #click="() => data.items = [...data.items,...dataFunc.buildData(100)]"/>
<FunctionButton name="Add 1000 recipes" #click="() => data.items = [...data.items,...dataFunc.buildData(1000)]"/>
<FunctionButton name="Delete all recipes" #click="() => data.items = dataFunc.deleteAll()"/>
<FunctionButton name="Edit all recipes" #click="() => data.items = dataFunc.updateAll(data.items)"/>
</div>
<ItemsContainer :items="data.items"/>
</main>
</div>
</template>
//ItemContainer.vue
<script setup>
import Item from '#/components/Item.vue';
const props = defineProps({
items: {type: Array, default: () => []},
})
</script>
<template>
<div
class='items-container w-full md:w-max bg-gray-800 dark:bg-gray-800 text-white dark:text-black mx-auto mt-12 p-4 space-y-4'>
<Item
v-for="item in items"
v-if="items.length > 0"
:key="item.id"
:meal="item"
/>
<p v-else class='text-white'>No items to display</p>
</div>
</template>
I tried to make this as generic as possible as trying to make a similar app on Vue and React so they could share this
import { v4 as uuidv4 } from 'uuid';
function create(){
const data = {
id: uuidv4(),
category: category[random(category.length)],
name: meals[random(meals.length)],
prepTime: randomTime(),
cookTime: randomTime(),
isFav: false
}
return data
}
function random(max){
return Math.round(Math.random() * 1000) % max;
}
const meals = [
"Roast Chicken", "Omelette", "Steak Pie", "Beef Stir Fry", "Fish And Chips", "Tomato And Prawn Risotto", "Pepperoni Pizza", "Cheesy Nachos", "Fajitas", "Baked Potato",
"Full English Breakfast", "Pancakes", "Chocolate Brownies", "Meatballs With Red Pepper Sauce", "Chicken Cesar Salad", "Beef Burger", "Chips","Macaroni Cheese", "Fillet Steak", "Chicken Wings",
"BBQ Ribs", "Tomato Soup", "Prawn Dim Sum", "Pork Gyozas", "Tomato Bruschetta", "Spring Rolls", "Beef Jerky", "Lasagne", "Spagetti Carbonara", "Salmon And Potatoes"
]
const category = [
"Breakfast", "Lunch", "Dinner"
]
function randomTime(){
return Math.round(Math.random() * 30)
}
function buildData(count){
const data = new Array(count);
for(let i = 0; i<count; i++){
data[i] = create();
}
return data;
}
function updateAll(currentArray){
return currentArray.map((item) => {
return{...item, name: meals[random(meals.length)], }
})
}
function updateOne(item){
return{...item, name: meals[random(meals.length)], }
}
function favouriteAll(currentArray = []){
return currentArray.map((item) => {
return {...item, isFav: true}
})
}
function deleteAll(){
const data = []
return data;
}
export {buildData, deleteAll, favouriteAll, updateAll, updateOne};
Check the console for errors.
If I take the functionality from HomeView.vue, your ItemContainer.vue and an Item.vue (which you didn't provided), then it works.
Check the working Vue SFC Playground
The code:
App.vue
<script setup>
import { ref, watch, reactive } from 'vue'
import ItemsContainer from './ItemContainer.vue';
const data = reactive({
items: []
})
console.log(data.items)
watch(() => data.items, (newValue) => {
alert(JSON.stringify(newValue))
}, {deep: true});
const addItem = () => {
data.items.push({ id: data.items.length, name: 'New Item'})
}
</script>
<template>
<ItemsContainer :items="data.items"/><br />
<button type="button" #click="addItem()">Add</button>
</template>
ItemContainer.vue is the same.
Item.vue
<script setup>
const props = defineProps({
meal: {
type: Object,
default: {undefined}
}
})
</script>
<template>
<div>
{{JSON.stringify(meal)}}<br />
</div>
</template>
UPDATE
I've updated the playground with your functions and it still does work well. The problem is somewhere else.

RouterLink does not render in the test (Vue test utils)

I am trying to test vue-router links but the RouterLink does not render, causing the error Cannot call text on an empty DOMWrapper.
Test:
const mkroute = { params: { test: "a" } };
const mkrouter = { push: jest.fn() };
jest.mock("vue-router", () => ({
useRoute: () => mkroute,
useRouter: () => mkrouter,
}));
describe("...", () => {
it("...", async () => {
const wrapper = mount(view);
console.log(wrapper.html());
expect(wrapper.find("a#test").exists()).toBe(true);
});
});
console.log:
<div>
<!--[object Object]-->
<!--[object Object]-->
<!--[object Object]-->
</div>
Versions:
Vue 3 / Vue test utils 2 / Vue router 4 / Jest 27
The unit tests (eg. jest or vitest) use the RouterLinkStub component: https://test-utils.vuejs.org/api/#routerlinkstub
Probably router-link-stub is visible in your html code.
So, to check whether it has been generated correctly, use the ref and findComponent method, for example:
<router-link :to="{ name: url }" ref="buttonAdd">
<button class="btn btn-sm btn-success btn-wtf">
<fa icon="plus" /> {{ $t("dictionary.add") }}
</button>
</router-link>
and a simple test:
it.only('buttonAdd rendered correctly', () => {
const link = wrapper.findComponent({ ref: 'buttonAdd' });
expect(link.exists()).to.equal(true);
expect(link.props().to).toStrictEqual({ name: 'AdminReturnsAdd' });
});

Cannot read properties of undefined (reading 'transition') - Swiper & VueJS & Ionic

I can't use the Swiper slideNext function in VueJS with Ionic.
I tried the demo codes from the official Swiper website, without success...
Here is my code:
<template>
<ion-page>
...
<ion-row class="mainRow">
<ion-col size="12">
<ion-row>
<div class="swiper">
<div
class="swiper-slide"
v-for="(question, index) in qst"
:key="`question_${index}`"
>
...
</ion-page>
</template>
<script>
setup() {
const swiperRef = ref(null);
const [slideSpace, setSlideSpace] = useState(0);
const swiper = new Swiper(".swiper", {
speed: 400,
slidesPerView: 1,
spaceBetween: slideSpace,
controller: {
control: swiperRef,
},
on: {
slideChange: function (e) {
setCurrentQuestion(e.activeIndex + 1);
},
},
});
const handleAnswerClick = (event, answer, question) => {
...
setTimeout(() => {
isCorrect && setScore((score) => score + 1);
event.target.setAttribute("color", "light");
swiper.slideNext(); // this line is problematic
checkIfComplete();
}, 1000);
};
The error :
it comes from the Swiper module

VueJS test-utils can't find element inside child component

I'm trying to use findComponent with find method to find a child component's element and set it's value. But every time I run test, it gives me Cannot call setValue on an empty DOMWrapper. error.
Test file
import { mount } from '#vue/test-utils';
import Create from './Create.vue';
// import State from '#/components/State.vue';
describe('it tests Create component', () => {
test('it emits create event and resets the form when form is valid and create button is clicked', async () => {
const div = document.createElement('div');
div.id = 'root';
document.body.append(div);
const expectedNameValue = 'TODO_NAME';
const expectedStateValue = 'Pending';
const wrapper = mount(Create, {
attachTo: '#root',
});
await wrapper.find(`input`).setValue(expectedNameValue);
await wrapper.findComponent({ ref: 'state-component' }).find('select').setValue(expectedStateValue);
await wrapper.find(`form`).trigger('submit');
expect(wrapper.emitted().create).toBeTruthy();
expect(wrapper.emitted().create[0]).toEqual([expectedNameValue]);
expect(wrapper.emitted().create[1]).toEqual(['Pending']);
expect(wrapper.find(`input[name='name']`).element.value).toEqual('');
expect(wrapper.find(`input[name='state']`).element.value).toEqual('Pending');
});
});
Create component
<template>
<form #submit.prevent="createTodo" class="flex gap-2 w-full">
<input class="flex-1 shadow rounded-md p-2 focus:ring-2 focus:ring-blue-900 focus:outline-none" type="text" placeholder="Todo Name" name="name" required/>
<State ref="state-component"/>
<button type="submit" class="rounded-md shadow text-white bg-blue-700 py-2 px-6">Create</button>
</form>
</template>
<script>
import State from '#/components/State.vue';
export default {
components: { State },
emits: ['create'],
methods: {
createTodo(event) {
const elems = event.target.elements;
const todo = { name: elems.name.value, state: elems.state.value };
this.$emit('create', todo);
elems.name.value = '';
elems.state.value = 'Pending';
}
}
}
</script>
<style scoped>
</style>
State component
<template>
<select id="state-select" class="rounded-md bg-green-200 text-white" name="state">
<option
v-for="(state, index) in states"
:selected="isSelected(state)"
:key="index"
>
{{ state }}
</option>
</select>
</template>
<script>
export default {
props: ["todo", "index"],
data() {
return {
name: "",
state: "",
states: ["Pending", "In Progress", "Done"],
};
},
created() {
if(!this.todo) return true;
this.state = this.todo.state;
this.name = this.todo.name;
},
methods: {
isSelected(equivalent){
return equivalent === this.state;
}
}
};
</script>
<style scoped></style>
I'm fairly new to VueJS so I'm open to all tips and tricks, thanks.
Some issues to fix:
You don't need to attach the component to the document, so remove that:
// ❌
// const div = document.createElement('div');
// div.id = 'root';
// document.body.append(div);
// const wrapper = mount(Create, { attachTo: '#root' });
// ✅
const wrapper = mount(Create);
The template ref to the State component would be the component's root element, so no need to find() the <select>:
// ❌
// await wrapper.findComponent({ ref: 'state-component' }).find('select').setValue(expectedStateValue);
^^^^^^^^^^^^^^^
// ✅
await wrapper.findComponent({ ref: 'state-component' }).setValue(expectedStateValue);
The emitted() object key is the event name, and the value is an array of of arrays, containing emitted data. You can verify the first create-event data contains another object with toMatchObject(object):
// ❌
// expect(wrapper.emitted().create[0]).toEqual([expectedNameValue]);
// expect(wrapper.emitted().create[1]).toEqual(['Pending']);
// ✅
expect(wrapper.emitted().create[0][0]).toMatchObject({ name: expectedNameValue, state: 'Pending' });
The last assertion tries to find input[name='state'], but that's actually a <select>, not an <input>:
// ❌
// expect(wrapper.find(`input[name='state']`).element.value).toEqual('Pending')
^^^^^
// ✅
expect(wrapper.find(`select[name='state']`).element.value).toEqual('Pending')
demo