Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

createInertiaApp not using "id" option for Vue root element #1358

Closed
swifthand opened this issue Dec 8, 2022 · 9 comments
Closed

createInertiaApp not using "id" option for Vue root element #1358

swifthand opened this issue Dec 8, 2022 · 9 comments

Comments

@swifthand
Copy link

swifthand commented Dec 8, 2022

Version:

  • nodejs version: 18.12.1

  • @inertiajs/vue3 version: 3.2.45

  • @inertiajs/inertia version: 0.11.1

  • @inertiajs/inertia-vue3 version: 0.6.0

  • ruby version: 3.1.2

  • rails version: 7.0.4

Describe the problem:

Per the documentation page "Client-side setup", a custom element ID can be specified for the root of the application:

By default, Inertia assumes that you have a root element with an id of app. If different, you can change this using the id property.

createInertiaApp({
 id: 'my-app',
 // ...
})

Something, somewhere is dropping the ball when attempting to use this capability. If an element with the ID exists named my-vue-root, it is not used when passed as the id option/argument to createInertiaApp.

I have tested this in a clean install, with the versions described above, using the simplest-possible example: a single component/page application with a stripped-down layout template, and bare minimum setup for Inertia/Vue. Different amounts of fiddling and banging has led to one of two main outcomes:

  1. A new <div> element is created within the intended root element with and id of app, ignoring the intended purpose.
  2. An error Uncaught (in promise) SyntaxError: "undefined" is not valid JSON in createInertiaApp.js:7:36, because this root element does not have the value data-page, or if I add data-page="" to the element, it fails for other reasons.

Reading the source of createInertiaApp.js I see that one can pass a page argument to createInertiaApp(). It is not documented in that file, but it seems one can use this by passing page as an object with properties component, props and url. This can "force" an initial page load to boot up with a particular component. However, the component does not load any data from the server (regardless of the url property in the page object), and worse, doing so causes a page refresh to leave the current route and re-render this initial page/component.

It "works" in that the specified component will mount within the element with id="my-app" instead of make a nested <div id="app">. So success on that, but then breaks routing on reloads. For example, if the page property in the argument is { component: "Home", url: "/" }, then one navigates to a Foo.vue page at /foo, and then reloads, the page does not return to /foo. Instead it is reset to show Home and the route is reset to /. There is a logic to that, but it is clearly not a desirable tradeoff just to force the proper use of a DOM element by id.

Steps to reproduce:

First, the simplest possible combination:

app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html>
  ...
  <body>
    <div id="my-vue-root">
      <%= yield %>
    </div>
  </body>
</html>

app/frontend/entrypoints/application.js:

import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/inertia-vue3';

const pages = import.meta.globEagerDefault('../Pages/**/*.vue');

createInertiaApp({
  id: 'my-vue-root',
  resolve: (name) => {
    return pages[`../Pages/${name}.vue`];
  },
  setup({ el, App, props, plugin }) {
    const vueApp = createApp({ render: () => h(App, props), });
    vueApp.use(plugin).mount(el);
  },
});

app/frontend/Pages/Home.vue:

<template>
  <div>
    <h1>Home Page</h1>
  </div>
</template>

Even though <div id="my-vue-root"> clearly exists, this produces the JavaScript error:

Uncaught (in promise) SyntaxError: "undefined" is not valid JSON
    at JSON.parse (<anonymous>)
    at exports.createInertiaApp (createInertiaApp.js:7:36)
    at application.js:56:1

Stepping through in the debugger, the createInertiaApp function is receiving the id property from the argument, but is trying to catch some data parameter page that does not exist yet, because the app is not yet created. Obviously the app and its page data cannot be created yet, as this is the create function itself!
(From packages/vue3/src/createInertiaApp.js):

export default async function createInertiaApp({ id = 'app', resolve, setup, title, page, render }) {
  const isServer = typeof window === 'undefined'
  const el = isServer ? null : document.getElementById(id)
  const initialPage = page || JSON.parse(el.dataset.page) // Line 7, where the error occurs
  ...
}

In an even stranger case, and I apologize for not isolating this better, I have managed to get a halfway failure, where this SyntaxError: "undefined" is not valid JSON error occurs, but the DOM itself appears to have created the <div id="app"> nested within my specified <div id="my-vue-root"> element, before attempting to use that element and failing anyway. The DOM in such an occasion looks like this:

<div id="my-vue-root">
  <div id="app" data-page="{'component':'Home','props':{},'url':'/','version':null}"></div>
</div>

Again, I can "force" the root to attach to <div id="my-vue-root"> if I instead initialize with a page property in the object argument:

createInertiaApp({
  id: 'my-vue-root',
  page: {
    component: "Home",
    url: "/",
  },
  resolve: ..., // same as above
  setup({ el, App, props, plugin }) { ... }, // same as above
});

However, again, doing this causes any and all reloads of the application, at any route (e.g. after navigating elsewhere) to reset and load Home at the route /. I feel like I understand why, but it makes me feel like this page option must truly be not intended for use in this way.

I am left wondering, based on the documentation, whether this is a bug, or if I am radically misunderstanding something about this id option for createInertiaApp?

Why this matters

I ask this because, in a more complex, real-world use, I wish to point Inertia at different root elements by id, in different situations. If this is not how the id option is intended to be used, I kindly request some documentation explaining what steps are required to properly use this id option for createInertiaApp.

@swifthand swifthand changed the title createIntertiaApp not using "id" option for Vue root element createInertiaApp not using "id" option for Vue root element Dec 8, 2022
@PerikiyoXD
Copy link

PerikiyoXD commented Mar 31, 2023

This just happened to me too.

Uncaught (in promise) SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data

app.blade.php

<!DOCTYPE html>
<html lang="en" data-bs-theme="dark" class="w-100 h-100">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
    <title>Document</title>
    @vite(['resources/js/app.js'])
    @inertiaHead
</head>
<body id="my-app" class="w-100 h-100">
</body>
</html>

app.js

...
const inertiaData = {
    id: 'my-app',
    resolve: name => {
        const pages = import.meta.glob('./Pages/**/*.vue', {eager: true})
        let page = pages[`./Pages/${name}.vue`]
        page.default.layout = page.default.layout || AppLayout
        return page
    },
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .mount(el)
    },
};
createInertiaApp(inertiaData);

For the "createInertiaApp" portion:

export default async function createInertiaApp({ id = 'app', resolve, setup, title, page, render }) {
  const isServer = typeof window === 'undefined'
  const el = isServer ? null : document.getElementById(id)
  const initialPage = page || JSON.parse(el.dataset.page) // Line 7, where the error occurs
  ...
}

It gets compiled to this JS:

let m = typeof window > "u", ... // Is this a valid "undefined" check? This isn't returning true or false, but stays undefined.

By this being set to undefined the JSON parse seems to just not go through.

I can provide more details, and this would be awesome to be fixed.

@zxdstyle
Copy link

update your blade.php : @inertia('my-app')

@reinvanimschoot
Copy link

I'm experiencing the same issue in React. Is there any solution to this or is it just a bug.

@RobertBoes
Copy link
Contributor

Yeah, the docs are incomplete on this. What's described in the docs only changes the client-side, server-side the rendered div also has to be changed for it to even work. For Laravel you'd have to use the approach @zxdstyle mentioned (@inertia('my-app') / https://legacy.inertiajs.com/releases/inertia-laravel-0.5.0-2022-01-07). It looks like for the Rails adapter there is no such option (https://github.com/inertiajs/inertia-rails/blob/master/app/views/inertia.html.erb), perhaps @BrandonShar can shine some light on this?

@PerikiyoXD
Copy link

PerikiyoXD commented Jul 17, 2023

Then, a solution would be updating the docs indicating the proper way of initializing inertia using the @inertia('<id>')

@reinink
Copy link
Member

reinink commented Jul 28, 2023

Hey! Thanks so much for your interest in Inertia.js and for sharing this issue/suggestion.

In an attempt to get on top of the issues and pull requests on this project I am going through all the older issues and PRs and closing them, as there's a decent chance that they have since been resolved or are simply not relevant any longer. My hope is that with a "clean slate" me and the other project maintainers will be able to better keep on top of issues and PRs moving forward.

Of course there's a chance that this issue is still relevant, and if that's the case feel free to simply submit a new issue. The only thing I ask is that you please include a super minimal reproduction of the issue as a Git repo. This makes it much easier for us to reproduce things on our end and ultimately fix it.

Really not trying to be dismissive here, I just need to find a way to get this project back into a state that I am able to maintain it. Hope that makes sense! ❤️

@reinink reinink closed this as completed Jul 28, 2023
@PerikiyoXD
Copy link

Add @inertia(<id>) as a possible initialization to the docs

@reinink
Copy link
Member

reinink commented Aug 25, 2023

Add @inertia(<id>) as a possible initialization to the docs

Yup, exactly, this works!

@inertia('my-custom-id')

And I agree, this should get added to the server-side setup page: https://inertiajs.com/server-side-setup

Going to open this issue as a reminder to do that 👍

@pedroborges
Copy link
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants