question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Issue with multiple slots in persistence layout.

See original GitHub issue

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?

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:2
  • Comments:30 (6 by maintainers)

github_iconTop GitHub Comments

13reactions
jamesburrow23commented, Sep 17, 2020

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:

carbon (1)

11reactions
claudiodekkercommented, Jun 8, 2020

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:

  1. The Inertia component gets created, and it’s render-method gets hit by Vue itself.
  2. Instead of rendering it’s own template, the “Inertia component” passes the “page”-component options (basically an object representation of the component) in to the renderer, which returns a VNode instance. To keep things simple, you can think of this as a DOM-ready, rendered instance of the component.
  3. Inertia checks whether the “page”-component options (the same ones used to create the rendered VNode just now) contains a 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:

  1. Prevent having to override our page component’s render method
  2. Intercept the component rendering from within the component itself 🤯

Unfortunately, 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:

  1. I created a “Persistent” Vue (layout) component, in which I mostly don’t use markup, except for things that I want to, well, persist across pages.
  2. In my 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)
  3. In my “page”-components themselves, I use Layouts in the same way how Inertia’s docs mostly describe using them.

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!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Build Modern Laravel Apps Using Inertia.js: Persistent Layouts
@abdosaeedelhassan +1 on this. I'm using multiple named slots and haven't found a way around to use persistent layouts.
Read more >
Laravel jetstream inertia persistent layout - Stack Overflow
If the header slot is in AppLayout aka the persistent one, you cannot use this way (because there is no slot yet?
Read more >
Structuring Layouts in InertiaJS applications - YouTube
Structuring Layouts in InertiaJS applications. 4.2K views 2 years ago. Constantin Druc. Constantin Druc. 3.21K subscribers. Subscribe.
Read more >
Pages - Inertia.js
Persistent layouts. While it's simple to implement layouts as children of the page components, it does force the layout instance to be destroyed...
Read more >
Layouts with Vue.js - How to Create Dynamic Layout ...
Static layout wrapper components. Next we take a look at how we can use an ordinary component, containing one or multiple slots for...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found