inertia
inertia copied to clipboard
createInertiaApp not using "id" option for Vue root element
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
ofapp
. If different, you can change this using theid
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:
- A new
<div>
element is created within the intended root element with and id ofapp
, ignoring the intended purpose. - An error
Uncaught (in promise) SyntaxError: "undefined" is not valid JSON
increateInertiaApp.js:7:36
, because this root element does not have the valuedata-page
, or if I adddata-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
.
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.
update your blade.php : @inertia('my-app')
I'm experiencing the same issue in React. Is there any solution to this or is it just a bug.
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?
Then, a solution would be updating the docs indicating the proper way of initializing inertia using the @inertia('<id>')
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! ❤️
Add @inertia(<id>)
as a possible initialization to the docs
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 👍