# Rails 7 Using React With Htm

> See how you can update your new Ruby on Rails 7 project to use HTM and React without a Node.js setup.

- Canonical: https://dennisokeeffe.com/blog/2022-02-18-rails-7-using-react-with-htm
- Published: 2022-02-18
- Updated: 2022-02-18
- Tags: ruby

Over the next two posts, I will be showing you two ways to setup a React frontend setup with your new Rails application:

1. A Node-less setup using import map to bring in `htm` and `react`.
2. A Node setup using `esbuild` (that will support TypeScript out of the box).

> It is worth noting upfront that today's post using `htm` is not necessarily my preference and changes how one would write React, but I felt it was worth exploring and sharing.

Source code can be found [here](https://github.com/okeeffed/demo-importpin-react-rails-7)

## Prerequisites

1. Basic familiarity with [Bundler](https://bundler.io/).
2. Basic familiarity with setting up a [new Rails project](https://guides.rubyonrails.org/getting_started.html).
3. Not necessary, but we will be adding JavaScript dependencies using [`importmap-rails`](https://github.com/rails/importmap-rails).

## Getting started

Assuming you are using Rails 7.x, we will be using the following command to install the necessary dependencies:

```s
# Setup
$ rails new demo-importpin-react-rails-7
$ cd demo-importpin-react-rails-7

# Creating the necessary files for the project
$ mkdir app/javascript/components
$ touch app/javascript/components/index.js app/javascript/components/htm_create.js

# Setting up a controller to render the React app
$ ./bin/rails g controller components index

# Pinning the dependencies that we will be using
$ ./bin/importmap htm react react-dom
```

In the above, we are doing the following:

1. Creating a new Rails project called `demo-importpin-react-rails-7`.
2. Creating a `app/javascript/components/index.js` file and `app/javascript/components/htm_create.js` file. These files will be used to add our `htm`-style React component and render helper.
3. Creating a controller called `components` and a `index` action. This will be added to our `routes.rb` as our a root route.
4. Pinning the `htm`, `react` and `react-dom` dependencies using `importmap-rails`. By default, this will pin the latest version of each dependency from the `jspm` CDN.

At this , we need to update our `config.routes.rb` file to use our new controller index method `components#index` as the root route:

```rb
Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Defines the root path route ("/")
  root "components#index"
end
```

Finally, our `config/importmap.rb` needs to look like this:

```rb
# Pin npm packages by running ./bin/importmap

pin 'application', preload: true
pin '@hotwired/turbo-rails', to: 'turbo.min.js', preload: true
pin '@hotwired/stimulus', to: 'stimulus.min.js', preload: true
pin '@hotwired/stimulus-loading', to: 'stimulus-loading.js', preload: true
pin_all_from 'app/javascript/controllers', under: 'controllers'
pin 'react', to: 'https://ga.jspm.io/npm:react@17.0.2/index.js'
pin 'react-dom', to: 'https://ga.jspm.io/npm:react-dom@17.0.2/index.js'
pin 'object-assign', to: 'https://ga.jspm.io/npm:object-assign@4.1.1/index.js'
pin 'scheduler', to: 'https://ga.jspm.io/npm:scheduler@0.20.2/index.js'
pin_all_from 'app/javascript/components', under: 'components'
```

Our `./bin/importmap` command early should have added most of the commands we need, but ensure that you have added `pin_all_from 'app/javascript/components', under: 'components'` at the bottom.

At this stage, our app is setup to be able to handle our React app.

## Updating our `application.js` file

At this point, we need to update our `app/javascript/application.js` file to reflect that we want to resolve the `app/javascript/components` directory that we created during setup:

```js
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails";
// import "controllers";
import "components";
```

At this point, we can now update our `app/javascript/components/index.js` file as our basic React app that is resolved by the application file:

But before we do that, we want to make our life a little bit easier by creating a help for `htm` to handle the binding of our `React.createElement` function to a tagged template string of our React component.

## The `htm createElement` helper

Inside of `app/javascript/components/htm_create_element.js`, we will create a helper that will be used to create our React component:

```js
import { createElement } from "react";
import htm from "htm";
export default htm.bind(createElement);
```

You can read the basics of `htm` on [GitHub](https://github.com/developit/htm), but the basic example it gives on how things work is the following:

```js
import htm from "htm";

function h(type, props, ...children) {
  return { type, props, children };
}

const html = htm.bind(h);

console.log(html`<h1 id="hello">Hello world!</h1>`);
// {
//   type: 'h1',
//   props: { id: 'hello' },
//   children: ['Hello world!']
// }
```

In our case with React, we wanted to pass the `createElement` function to `htm.bind` instead of the example `h` function provided above. This will become our helper.

> Note: although the approach we wrote with the helper is more verbose, it also looks like you could bypass what we are about to do and use `import { html } from 'htm/react';` instead. See [usage](https://github.com/developit/htm#usage) for more.

At this stage, we are ready to use our helper to render our React app.

## Updating our application code

Back in `app/javascript/components/index.js`, we can now add the following:

```js
import { render } from "react-dom";
import h from "components/htm_create_element";

const App = () => {
  return h`<div>Hello, Rails 7 Importpin!</div>`;
};

render(h`<${App} />`, document.getElementById("root"));
```

Here, we are importing in our helper `h` which will enable us to render our React app.

As you will see with the above code, it moves away from the standard React syntax you would be familiar with as all the component code needs with be added as a tagged template to the `h` function.

We are now ready to set up our view to render the app.

## Rendering the final app

Update the `app/views/components/index.html.erb` generated during our set up to have the following:

```html
<h1>Components#index</h1>
<div id="root"></div>
```

As denoted in our application code from the previous section, we are rendering the app on the `#root` element.

If we now run our Rails server using `bin/rails s`, we can head to our `localhost:3000` page and we should see our React app.

![React app](https://cdn.dennisokeeffe.com/assets/2022-02-18-rails-7-using-react-with-htm/1-application-running.webp)

Awesome! Our app is setup and ready to go.

## Summary

Today's post demonstrated how to setup a Node-less version of React to run with Rails 7.

This required the use of `importpin-rails` and `htm` as a dependency. It also required the use of tagged templates, which is a departure from those familiar with React syntax.

In my personal opinion, I do not enjoy the idea of using tagged templates for a larger application, but in the next post I will be exploring a setup of Rails 7 with ESBuild that will bring us back to familiar territory.

## Resources and further reading

- [Bundler](https://bundler.io/)
- [DHH's YouTube video on this](https://www.youtube.com/watch?v=k73LKxim6tw)
- [Source code](https://github.com/okeeffed/demo-importpin-react-rails-7)
- [Rails Getting Started](https://guides.rubyonrails.org/getting_started.html)
- [`htm`](https://github.com/developit/htm)
- [`importmap-rails`](https://github.com/rails/importmap-rails)

_Photo credit: [`thebrownspy`](https://unsplash.com/@thebrownspy)_
