06 Jul 2023
Rails 7 introduced some new ways of interacting with javascript. It introduced importmaps which are a new way of pinning
your js files to your project. It also made turbo the default for frontend development which mostly got rid of the need
to have frontend javascript. One thing that I miss about rails 6 however was the quick and easy integration with tools
like inertiajs. This article goes into the new way to install it so I don’t forget in the future.
Small disclaimer- most of the content of this article has been pieced together from the docs and other blogs. I didn’t
find a current blog that had all of the pieces in the correct order however to end up with a working version of rails
and react. Thats what I hope to accomplish here.
- Install Rails and skip dependencies
rails new exampleapp --skip-javascript --skip-asset-pipeline
- Install inertia rails and vite rails
bundle add inertia_rails
bundle add vite_rails
- Execute the vite installer
- Go to
/app/views/layouts/application.html.erb
and change the following-
- Remove
<%#= stylesheet_link_tag "application" %>
(We will add the styles in our javascript)
-
Change <%= vite_javascript_tag 'application' %>
to <%= vite_javascript_tag 'application.jsx' %>
- Install vite-plugin-rails and @vitejs/plugin-react
yarn add -D vite-plugin-rails @vitejs/plugin-react react react-dom @inertiajs/react
Make sure your vite.config.ts looks like the following-
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react'
import ViteRails from 'vite-plugin-rails'
export default defineConfig({
plugins: [
react(),
ViteRails(),
],
})
- In
app/frontend
add two new folders- css and pages
In pages we can add our first test file- home.jsx with the following contents
export default function home() {
return (
<div className="mt-16 mx-auto max-w-5xl">
<h1 className="text-3xl">Hello home</h1>
</div>
);
}
- In
app/frontend/entrypoints
you should already have a file called application.js change the name to application.jsx
and add the following
import {createInertiaApp} from '@inertiajs/react'
import {createRoot} from 'react-dom/client'
import '../css/styles.css' // We are going to add this in step 10
createInertiaApp({
resolve: name => {
const pages = import.meta.glob('../pages/**/*.jsx', {eager: true})
return pages[`../pages/${name}.jsx`]
},
setup({el, App, props}) {
createRoot(el).render(<App {...props} />)
},
})
- Generate a controller using rails’s default generator
rails g controller home index
You can delete the app/views/home/index.html.erb
file that is created
In the home_controller, change it as follows-
class HomeController < ApplicationController
def index
render inertia: 'home'
end
end
Then add your route to the routes file-
Rails.application.routes.draw do
root "home#index"
end
- Now we can install tailwindcss-
yarn add -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Configure your template paths in the generated tailwind.config.js
as follows
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/views/**/*.html.erb",
"./app/frontend/**/*.{js,ts,jsx,tsx,css}",
],
theme: {
extend: {},
},
plugins: [],
}
- Add the styles.css file to
app/frontend/css
and paste the following
@tailwind base;
@tailwind components;
@tailwind utilities;
- Start up your server and see if it works!
foreman start -f Procfile.dev
You should now have a working rails inertiajs react setup. My recommendation is to install rspec and follow along in the
docs to use the built in testing support. Enjoy!
25 Sep 2022
So I decided to try something new just to keep myself sharp. I decided to manually install inertia and configure it for use with vue3 and vite. I hit a few small snags but I’m going to document them all here to hopefully not run into the same issues again.
Jump to Solution Already…
Install Inertia
The initial installation was exactly the way the docs presented it. I started to run into some issues when I got to the client side installation.
This is what the docs show you for the client side installation.
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/inertia-vue3'
createInertiaApp({
resolve: name => require(`./Pages/${name}`),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
})
The first thing you may notice is that vite does not allow you to use require directly. If you switch to import it would look something like this-
resolve: name => import(`./Pages/${name}`),
Easy enough- but if you run this you will get a warning in the vite compiler
createInertiaApp({
resolve: name => import(`./Pages/${name}`),
^
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
The above dynamic import cannot be analyzed by Vite.
First thing I tried was to add .vue to the end of the import. Something like this-
resolve: name => import(`./Pages/${name}.vue`),
But you will find that vue files placed in subdirectories will not resolve. For instance, if I wanted to have a file in Pages/books/Index.vue
then it would not be able to find this file.
So what is the solution? Globs…
TLDR use Glob imports
First you need a function to parse out the name into the directory and file name. Something like this-
/**
* Imports the given page component from the page record.
*/
function resolvePageComponent(name, pages) {
for (const path in pages) {
if (path.endsWith(`${name.replace('.', '/')}.vue`)) {
return typeof pages[path] === 'function'
? pages[path]()
: pages[path]
}
}
throw new Error(`Page not found: ${name}`)
}
Then you need to use this function to resolve your component names.
resolve: (name) => resolvePageComponent(name, import.meta.glob('./Pages/**/*.vue')),
All together it will look like this-
function resolvePageComponent(name, pages) {
for (const path in pages) {
if (path.endsWith(`${name.replace('.', '/')}.vue`)) {
return typeof pages[path] === 'function'
? pages[path]()
: pages[path]
}
}
throw new Error(`Page not found: ${name}`)
}
createInertiaApp({
resolve: (name) => resolvePageComponent(name, import.meta.glob('./Pages/**/*.vue')),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
})
PS- Credit goes to laravel-vite for the answer to this problem!
But wait there’s more!
I also was attempting to get Laravel Data up and running so that I could use the Typescript transformer and import my types. (What can I say, I pay close attention to the Laracons…)
First of all there are two packages you will need in order to use the Typescript functionality which is something I wasn’t aware of coming into it.
You will need
composer require spatie/laravel-data
&&
composer require spatie/typescript-transformer
After publishing your config for the typescript-transformer you will add the DataTypeScriptTransformer to the transformers in typescript-transformer.php like so-
'transformers' => [
...
\Spatie\LaravelData\Support\TypeScriptTransformer\DataTypeScriptTransformer::class,
],
Now when you add the #[TypeScript]
attribute to your Data object you will be able to generate your typescript files by running php artisan typescript:transform
.
Finally and crucially, you will need to add this new type file to your vite.config.js file like so
compileroptions: {
types: [
...
"./resources/types/generated",
]
}
Thats it! Now you should be able to use your type in your Vue3 files like so- App.Data.Books.BooksData
22 Sep 2022
I’m posting this so that I remember in a few years when this happens again…
We had a very interesting error that simply said- SQLSTATE[HY093]: Invalid parameter number
. The query that it showed looked fine and so I spent a significant amount of time scratching my head wondering if I was losing my marbles.
Here was the query-
select * from posts where uuid in (random-uuid-here)
Looks fine right? Well turns out we were passing an array of an array into this whereIn- something that looked similar to this-
[
[
'uuid_1',
'uuid_2',
'uuid_3',
]
]
And this generated a query that looked like this-
select * from posts where uuid in (?)
It was attempting to bind each uuid to that one parameter so it gave the invalid parameter number error. What we needed was something closer to this-
select * from posts where uuid in (?, ?, ?)
The solution was to flatten the array down to a single depth so whereIn would know to bind the correct number of parameters.
Future me- if you ever read this, make sure you pass flat arrays into whereIn. Pretty please?