Conditional returning inside <script lang="ts" setup>? - vuejs3

https://vuejs.org/api/sfc-script-setup.html
Is it possible to have conditional returns inside that setup script?
<script lang="ts" setup>
onErrorCaptured((e) => {
error.value = e;
return true;
});
if (error.value) return { error };
return {};
</script>

Related

Why the context.slots in the Vue setup() function has an empty object?

Accordingly to this Issue it should work with the current version v3.2.x.
But it doesn't.
Here is the playground:
const { createApp } = Vue;
const myComponent = {
template: '#my-component',
setup(props, { slots }) {
console.log(slots)
}
}
const App = {
components: {
myComponent
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
<my-component>Default
<template #footer>Footer</template>
</my-component>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script type="text/x-template" id="my-component">
<div>
<slot></slot>
<hr/>
<slot name="footer"></slot>
</div>
</script>
The solution was provided by Duannx.
With console.log(slots) they are listed correctly.
{
"footer": (...n)=>{o._d&&Tr(-1);const r=hn(t);let s;try{s=e(...n)}finally{hn(r),o._d&&Tr(1)}return s},
"default": (...n)=>{o._d&&Tr(-1);const r=hn(t);let s;try{s=e(...n)}finally{hn(r),o._d&&Tr(1)}return s}
}
Explanation
JSON.stringify doesn't show the slots since they are functions.
Here is the explanation from the MDN Docs JSON.stringify():
undefined, Function, and Symbol values are not valid JSON values. If any such values are encountered during conversion, they are either omitted (when found in an object) or changed to null (when found in an array). JSON.stringify() can return undefined when passing in "pure" values like JSON.stringify(() => {}) or JSON.stringify(undefined).
Example
console.log("JSON.stringify(() => {}): " + JSON.stringify(() => {}));
console.log(JSON.stringify({ "func": function () {}, "lmbd": () => {} }))

Svelte - UI does not get rendered which data I fetch from API

This is my script-tag:
<script lang="ts">
import Card from "../../components/Card.svelte";
import { onMount } from "svelte";
let show_all = false;
let data: Array<{id: number, image_url: string, description: string, link: string, title: string}> = [];
onMount(async () => {
try {
console.log(data)
let response = await fetch("http://localhost:4000/entries");
data = await response.json();
} catch(err) {
console.log(err);
}
});
const getData = async () => {
console.log(data)
if (!show_all) {
return data.slice(0, 12);
} else {
return data;
}
</script>
I render the data like this:
{#await getData()}
<p>...waiting</p>
{:then array}
{#each array as item (item.id)}
<Card image_url={item.image_url} description={item.description} link={item.link} title={item.title} />
{/each}
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
Fetching the data from the API works fine, but it seems to getData() function works with the empty array, not with data after it has been updated.
What am I doing wrong here? I thought using the onMount hook would guarantee that the UI gets rendered after the data was fetched.
Can anyone help me to fix this
The order is also logged here:
getdata
hook
hook should be called before getData
The problem is, as you mentioned, the order. getData() runs before the onMount callback. I assume waiting... should be shown while the data is fetched and the displayed data should also react to changes of show_all?
Here's one way in case data isn't otherwise needed inside the script tag
<script lang="ts">
import Card from "../../components/Card.svelte";
let show_all = false;
const getData = async () => {
try {
let response = await fetch("http://localhost:4000/entries");
return await response.json();
} catch(err) {
console.log(err);
}
}
</script>
{#await getData()}
<p>...waiting</p>
{:then data}
{#const dataToBeShown = show_all ? data : data.slice(0, 12)}
{#each dataToBeShown as item (item.id)}
<Card {...item} />
{/each}
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
in case it is
<script lang="ts">
import Card from "../../components/Card.svelte";
let show_all = false;
let data: Array<{id: number, image_url: string, description: string, link: string, title: string}> = [];
$: dataToBeShown = show_all ? data : data.slice(0, 12)
const getData = async () => {
try {
let response = await fetch("http://localhost:4000/entries");
data = await response.json();
} catch(err) {
console.log(err);
}
}
</script>
{#await getData()}
<p>...waiting</p>
{:then _}
{#each dataToBeShown as item (item.id)}
<Card {...item} />
{/each}
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
Since the property names seem to match >> <Card {...item} />

Convert options api to composition api for vue3 - v-model binding and watch

I have the following working code for a search input using options API for component data, watch and methods, I am trying to convert that to the composition api.
I am defining props in <script setup> and also a onMounted function.
<template>
<label for="search" class="hidden">Search</label>
<input
id="search"
ref="search"
v-model="search"
class="border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm h-9 w-1/2"
:class="{ 'transition-border': search }"
autocomplete="off"
name="search"
placeholder="Search"
type="search"
#keyup.esc="search = null"
/>
</template>
<script setup>
import {onMounted} from "vue";
const props = defineProps({
routeName: String
});
onMounted(() => {
document.getElementById('search').focus()
});
</script>
<!--TODO convert to composition api-->
<script>
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {
// page.props.search will come from the backend after search has returned.
search: this.$inertia.page.props.search || null,
};
},
watch: {
search() {
if (this.search) {
// if you type something in the search input
this.searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
this.$inertia.get(route(this.routeName));
}
},
},
methods: {
searchMethod: _.debounce(function () {
this.$inertia.get(
route(this.routeName),
{ search: this.search }
);
}, 500),
},
});
</script>
What I am trying to do is convert it to the composition api. I have tried the following but I can't get it to work at all.
let search = ref(usePage().props.value.search || null);
watch(search, () => {
if (search.value) {
// if you type something in the search input
searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
Inertia.get(route(props.routeName));
}
});
function searchMethod() {
_.debounce(function () {
Inertia.get(
route(props.routeName),
{search: search}
);
}, 500)
}
Any help or pointers in how to convert what is currently in <script> into <script setup> would be greatly appreciated thanks.
I managed to get this working with the below!
<script setup>
import {onMounted, ref} from "vue";
import {Inertia} from "#inertiajs/inertia";
const props = defineProps({
route_name: {
type: String,
required: true
},
search: {
type: String,
default: null
}
});
const search = ref(props.search);
onMounted(() => {
search.value.focus();
search.value.addEventListener('input', () => {
if (search.value.value) {
searching();
} else {
Inertia.get(route(props.route_name));
}
});
});
const searching = _.debounce(function() {
Inertia.get(route(props.route_name), {search: search.value.value});
}, 500);
</script>

Use injected variables (nuxt.firebase) in composition api

I'm using the composi api in my Vue project and the nuxt.js firebase module, I would like to call variables injected into modules, such as $ fireAuth, but I didn't find a solution.
Below is a small code training of how I would like it to work:
export default createComponent({
setup(_props, { root }) {
root.$fireAuth= ..
}
}
// or
export default createComponent({
setup(_props, { root , $fireAuth }) {
}
}
I have a work-around for this and it works! (For now.)
Create a dummy component (ex. AppFirebase.vue)
<template></template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
created() {
this.$emit("init", this.$fire);
},
});
</script>
Accessing NuxtFireInstance (ex. SomeComponent.vue)
<template>
<fire #init="initFB"></fire>
</template>
<script lang="ts">
import {
defineComponent,
reactive,
} from "#nuxtjs/composition-api";
import fire from "#/components/AppFirebase.vue";
export default defineComponent({
components: { fire },
setup() {
let _fire: any = reactive({});
const initFB = (fire: any) => {
_fire = fire;
};
const signout = async () => {
try {
await _fire.auth.signOut().then(() => {
// do something
});
} catch (error) {
console.log(error);
}
};
return {
initFB,
_fire,
signout,
};
},
});
</script>
Rickroll if you got it working!

setup properly Google Tag Manager on Nextjs with dataLayer?

What I have
My _document file:
import NextDocument, { Head } from 'next/document';
const GTM_TRACKING_ID = 'GTM-ID';
class WebAppDocument extends NextDocument {
render() {
return (
<html lang="es">
<Head>
{ /* Global Tag Manager (gtm.js) */}
{ /* => load GTM scripts according to environment variable */ }
<script dangerouslySetInnerHTML={{ __html: `window.dataLayer = window.dataLayer || []` }} />
<script
dangerouslySetInnerHTML={{
__html: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${GTM_TRACKING_ID}');
`,
}}
/>
</Head>
</html>
);
}
}
My _app file:
import NextApp from 'next/app';
const pushEvent = ({ event, pagePath, pageTitle }) => {
window.dataLayer.push({
event,
pagePath,
pageTitle,
});
};
class WebApp extends NextApp {
componentDidMount() {
// push data to Google Tag Manager
pushEvent({
event: 'PageView',
pagePath: window.location.pathname + window.location.search + window.location.hash,
pageTitle: document.title,
});
}
}
What I want
I've seen the example and used Router.events.on('routeChangeComplete', url => dataLayer.push({})) to execute dataLayer. Is this the best approach? Or is it better to run the code to populate the dataLayer in componentDidMount.
I want to load GTM scripts according to environment variable. How could this be achieved?

Resources