I have used a persistence layout where two slots (scoped slots) are present. Now I can't use that multiple scoped slots in my pages to push content in a layout. Any workaround on this?
Here is something that I was trying to do.
<template>
<main>
<slot name="header"/>
<slot/>
</main>
</template>
<script>
export default {
name: "Layout",
}
</script>
and my page is something like this:
<template>
<div>
page content
</div>
</template>
<script>
export default {
name: "Page",
layout: (h, page) => h(Layout, [page])
}
</script>
How can I pass the content of the header slot? I can do the following, as mentioned in https://github.com/inertiajs/inertia-vue/pull/87#issuecomment-536436890
export default {
layout: (h, page) => {
return h(Layout, {
scopedSlots: {
header() {
return h('span', 'Header')
}
}}, [page])
},
}
But I have not just a simple "Header" text in span. So I guess it's not that easier to write the whole content of the header in that way.
Any suggestion what should I do?
Use slot in Header from your page component:
export default {
name: "Page",
layout: (h, page) => {
return h(Layout, [
page,
h('span', {
slot: 'header'
}, 'Header')
])
}
}
Thanks for the reply. Yes, I can do that and I have mentioned above as well. The problem is, there will not just a span in the header, rather it will have few more dom. And writing dom in a functional way is not feasible as it will be different in pages and tedious as well. I tried JSX as well but didn't work. Can I pass a component in that h(...) ??
export default {
name: "Page",
layout: (h, page) => {
return h(Layout, [
page,
h('div', {
slot: 'header'
}, [
h('div', 'Other component'),
h('div', 'Other component')
])
])
}
}
It is recommended to read the "Render Functions" part of the Vue.js documentation: https://vuejs.org/v2/guide/render-function.html
Thank you @ycs77 for the suggestion. As per your suggestion, I did something similar to this and now I am stuck with another problem:
export default {
name: "Page",
layout: (h, page) => {
return h(Layout, [
page,
h(InertiaLink, {
props: {
href: `/users/${this.userId}`,
},
domProps: {
innerHTML: "Update User",
},
slot: "actions",
}),
])
}
}
In the above code, everything working perfectly except dynamic user id this.userId. Of course, it will not work because layout function will be called later hence this scope will be different.
Are there any ways to pass component scope (or data) to the layout? or any other workaround?
I have the same problem and I can't solve it.
But I send a new issue. inertiajs/inertia-vue#130
@puncoz There is a solution for now (Reference for https://github.com/inertiajs/inertia-vue/issues/130#issuecomment-640269331):
export default {
name: "Page",
layout: (h, page) => {
return h(Layout, [
page,
h(InertiaLink, {
props: {
href: `/users/${page.context.props.userId}`, // Change this line
},
domProps: {
innerHTML: "Update User",
},
slot: "actions",
}),
])
}
}
Sorry for not replying to this earlier (like I did on solution referred to above).
While the above solution indeed works, is only a partial & programmatic workaround to this issue by intercepting Vue's render cycle. While it would allow you to use multiple slots, it's still far from perfect, and it's not exactly what I would call the '_great developer experience_' that most of us are used to from working with Vue.
I did some source diving and looked into how to solve this earlier. Essentially, the way that persistent layouts work in Inertia is like this:
render-method gets hit by Vue itself.layout-property/function, which it then calls as if it was the Vue render method, passing in the already-rendered VNode/rendered instance as a 'child'While this might seem somewhat odd, it is actually quite cool, as this allow us to:
render methodUnfortunately, this also means that while Vue at this point hasn't finished rendering yet, but * the "Page"-component is already done rendering* (which gets injected into the Layout), we cannot use template slots, unless defined programmatically like described previously.
If you think of it using Vue terms, it also makes sense, since you can (mostly) only pass slots into a child component, but you cannot pass child-slots into a parent.. (In this case, the Layout is the parent component, and the Page is the child component)
Anyway, bad news aside, this got me onto another idea, which is to combine both approaches. Here's what I ended up doing:
app.js, I modified the resolveComponent method to hijack Inertia before it hits Vue's rendering process, and always inject the persistent layout on the "page"-component's options (as the resolveComponent method is simply how Inertia resolves the pages / the Vue component options that get used in the renderer described earlier)Complexity aside, this allows me to keep persistent things (such as notifications etc. and the user's browser-interaction-state of it) in the "Persistent"-layout, and communicate to/from it using Vuex. The important thing to do here (IMO) is to keep the html markup/styling on the Layout component as much as possible, and to keep the "Persistent"-layout as markup-free as possible, as to provide an "invisible" layer of sorts. As far as solutions go, I think this is a pretty decent one.
Long story short, or in case that wasn't clear / copy and paste-able enough, here's an example of how this would all get wired up:
app.js
import PersistentLayout from "./Layouts/Persistent";
import Store from "./VueXStore";
new Vue({
store: Store,
render: h => h(InertiaApp, {
props: {
initialPage: JSON.parse(app.dataset.page),
resolveComponent: name => {
const componentOptions = require(`./Pages/${name}`).default;
componentOptions.layout = (h, page) => h(PersistentLayout, [page]);
return componentOptions;
},
},
}),
}).$mount(app)
VueXStore.js
import Vue from 'vue';
import Vuex, { Store } from 'vuex';
Vue.use(Vuex);
export default new Store({
state: {
count: 1
},
mutations: {
increment (state) {
// mutate state
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Layouts/Persistent.vue
<template>
<div>
<persistent-component />
<slot />
Example VueX Counter: {{ count }}
</div>
</template>
<script>
import { mapState } from "vuex";
import PersistentComponent from "../Components/PersistentComponent";
export default {
components: {
PersistentComponent
},
computed: {
...mapState([
'count',
])
}
}
</script>
Layouts/Primary.vue
<template>
<div>
<div class="this-is-my-template">
<slot name="header" />
</div>
<slot />
</div>
</template>
Pages/MyPage.vue
<template>
<layout title="Home">
My Page Content
<button @click="increment">Increment Persistent Counter</button>
<div slot="header">
My Header Content
</div>
</layout>
</template>
<script>
import { mapActions } from "vuex";
import Layout from "../../Layouts/Primary";
export default {
components: {
Layout
},
methods: {
...mapActions([
'increment'
])
}
}
</script>
Hope it helps!
@claudiodekker Thanks for the lengthy and helpful work around!
@claudiodekker - Question for you....Have you used this approach to persistent layouts in conjunction with code splitting? Today I implemented code splitting in my application but my persistent layout is no longer being rendered.
Was curious if you've seen this before
@jamesburrow23 Hmm.. No, I haven't. Sorry.
To be honest, I've also since migrated to a different approach, at which I threw out VueX altogether. My main reasoning for this is that Inertia was designed to have it's state managed on the back-end, and I wanted to prevent having 'two' sources of truth.
What I've decided to do instead, is to use Vue's built-in provide and inject. It's not reactive, but it solves the same issue:
The most appropriate answer to this question is probably also the one provided by @ycs77, as that's how Vue's render method is supposed to be used (according to their forums and documentation)
@claudiodekker thanks for the reply! I've used provide and inject in various places but unfortunately, I've got this component that needs to be persistent so I'll keep hacking at it 馃憤
I'll check out that other comment, thanks!
For anyone who stumbles across this issue, I was able to work around the lack of slots by using this package: https://github.com/LinusBorg/portal-vue
I have portal-target components in my persistent layout where i'd normally use a conventional vue named slot. Then in my inertia pages I can push my content into those portal targets using <portal to="target-name">
With this setup my code splitting works again since my inertia page component is invoking PersistentLayout, obviously you lose some of the vue goodness like scopedProps but most of your state should be in a vuex store anyways 馃槃
here is a simple example:

Most helpful comment
Sorry for not replying to this earlier (like I did on solution referred to above).
While the above solution indeed works, is only a partial & programmatic workaround to this issue by intercepting Vue's
rendercycle. While it would allow you to use multiple slots, it's still far from perfect, and it's not exactly what I would call the '_great developer experience_' that most of us are used to from working with Vue.I did some source diving and looked into how to solve this earlier. Essentially, the way that persistent layouts work in Inertia is like this:
render-method gets hit by Vue itself.layout-property/function, which it then calls as if it was the Vuerendermethod, passing in the already-rendered VNode/rendered instance as a 'child'While this might seem somewhat odd, it is actually quite cool, as this allow us to:
rendermethodUnfortunately, this also means that while Vue at this point hasn't finished rendering yet, but * the "Page"-component is already done rendering* (which gets injected into the Layout), we cannot use template slots, unless defined programmatically like described previously.
If you think of it using Vue terms, it also makes sense, since you can (mostly) only pass slots into a child component, but you cannot pass child-slots into a parent.. (In this case, the Layout is the parent component, and the Page is the child component)
Anyway, bad news aside, this got me onto another idea, which is to combine both approaches. Here's what I ended up doing:
app.js, I modified theresolveComponentmethod to hijack Inertia before it hits Vue's rendering process, and always inject the persistent layout on the "page"-component's options (as theresolveComponentmethod is simply how Inertia resolves the pages / the Vue component options that get used in the renderer described earlier)Complexity aside, this allows me to keep persistent things (such as notifications etc. and the user's browser-interaction-state of it) in the "Persistent"-layout, and communicate to/from it using Vuex. The important thing to do here (IMO) is to keep the html markup/styling on the
Layoutcomponent as much as possible, and to keep the "Persistent"-layout as markup-free as possible, as to provide an "invisible" layer of sorts. As far as solutions go, I think this is a pretty decent one.Long story short, or in case that wasn't clear / copy and paste-able enough, here's an example of how this would all get wired up:
app.js
VueXStore.js
Layouts/Persistent.vue
Layouts/Primary.vue
Pages/MyPage.vue
Hope it helps!