[Part 2] Hotwire - between the HTML (server side) and the Javascript rendering. What to choose?

After the analysis of HTML (server side) rendering in the [Part 1], we are focusing on purely Javascript rendering in this article. In this approach whole GUI work is delegated to Client side (browser) and server performs only data operations (API). Since whole interface is handled on the Client side and there are no multiple pages (in standard HTML rendering sense) we call these applications "Single Page Applications" (SPA). Frameworks that popularized SPA-s development are Angular, React, Vue and Ember.

[Part 2] Hotwire - between the HTML (server side) and the Javascript rendering. What to choose?

Article parts

To tackle the topic of web application interface (GUI) rendering and to compare available approaches, the article is split into following parts:

GitHub Repository

There are two solutions presented in this article and complete implementation of solutions shown in this article can be cloned from GitHub repositories:

https://github.com/emir-gradient/cursor-tracking-simple-javascript-example

https://github.com/emir-gradient/greeting-app-react

Concept of Javascript Rendering

Javascript is a scripting or programming language that can be executed in the web browser (as the part of single HTML page) and can add dynamic behavior to the web page. Looking into basic operations, it allows us to create, modify, remove and animate HTML elements as the result of some event happening ("mouse click" as user interaction is one example).

Being able to manipulate all HTML elements on the web page allows as to performin Javascript rendering. So, instead of server building interface on every user's request, that work is delegated to the browser side. More precisely, to the Javascript side.

Relevant term, when it comes to Javascript rendering, is "Document Object Model" (DOM). DOM is the data representation of the objects that comprise the structure and content of a HTML document on the web. Javascript has access to DOM, to manipulate it, and as the result of that manipulation, presented web interface changes (browsers react to DOM manipulations by rendering applied DOM manipulations).

The following example describes Javascript usage to make interactive web page. It includes section for rendering elements to the HTML body, and to add interactivity to it. Note how HTML body in this example consists of the <script> tag only.

This example shows current coordinates of the cursor on the webpage. Also, it updates those coordinates in the real time whenever cursors moves.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Simple JS User Interaction Example</title>
</head>
<body>

<script src="js/simple-js-user-interaction-example.js"></script>
</body>
</html>

js/simple-js-user-interaction-example.js

// INITIAL INTERFACE CREATION PART

// Invent new div HTML element
const titleDiv = document.createElement('div');

// Configure its content.
titleDiv.innerHTML = '<h1>Cursor position on the page:</h1>';

// Append it to the bottom of HTML body element
document.body.append(titleDiv);

const cursorPositionDiv = document.createElement('div');
cursorPositionDiv.innerText = 'x: 0.00, y: 0.00';
document.body.append(cursorPositionDiv);

// INTERACTIVE PART
// Function to be executed on every mouse move over the web page window.
window.onmousemove = function (event) {
    // Fill in `cursorPositionDiv` with content of the current mouse coordinates.
    cursorPositionDiv.innerText = `x: ${event.clientX}, y: ${event.clientY}`;
};
Blog pictureBlog picture

SPA Frameworks and Four Main Concerns

Looking at previously presented relatively simple example, one can imagine how complete web application that solves relatively large set of business problems might become hardly maintainable and unstable. To point couple of reasons out:

  • Absence of consistency: In one section innerHTML of element is modified, in another one it is innerText.
  • Absence of structure: There are simply two sections (marked by comments), one for Initial Rendering, and another one for Interactivity Updates.
  • Absence of cross-browser support: Some of the operations might not be supported in all browsers.
  • Absence of performance optimization: There is simple function that replaces innerText of cursorPositionDiv on every 'mousemove' event being emitted. Meaning that browser will refresh given `div` content whenever it is possible for it to do so. This rendering operation is completely independent of any other rendering operations that might be needed in larger application.

Still, since previous example does single initial rendering of the content, and single interactivity operation, one can safely argue that implementation is completely fine and every additional improvement on aforementioned points is an overkill.

Now, if we look at any more complex example, we see that there is a need to first design some structured, consistent, cross-browser supported and highly performant Javascript library. When that structure is enforced at some level higher than language-level, we call that library Framework, and when that framework strives to solve these four main concerns for Javascript rendering (on multiple business complexity levels), we call it "SPA Framework".

Popular SPA Frameworks in the moment of writing this article are Angular, React, Vue and Ember.

Choice of SPA Framework for "Greeting App"

To be able to compare different web rendering approaches (in [Part 4] of this article) intent is to develop the same "Greeting App" example application using them. Specification (and functionality scope) can be read in HTML and HTML partials rendering [Part 1],

To choose between these SPA frameworks, in general, we should consider multiple factors relevant for the business case we are trying to solve. When it comes to the "Greeting App" and its scope, every of these frameworks is sufficient, so without particular reason, in this article React.js is utilized.

Starting React Application Development

To develop React web application in this article, "Create React App" is utilized. It provides us with solid starting point, to be able to work on our React application.

To create initial application, execute the following command:

npx create-react-app greeting-app

Result of this command is that initial React template will be downloaded and configured in "greeting-app" directory. Inside that directory is root of the project. To run your application in development environment, you need to execute configured 'start' script:

npm start

As the result, development server starts, it opens the browser and loads our React application.

Blog picture

Rendering using React

Entry point for every SPA application is initial HTML file with one empty div. That div, after loading SPA framework, is filled by SPA framework rules (in this case React). Body of initial HTML that we get as result of 'create-react-app' looks as follows:

public/index.html

<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
</body>

As we can see, there is single <noscript> tag and single empty div having "root" id. <noscript> tag is HTML tag whose content is ignored by all browsers that support Javascript. In case when web browser does not support it, it actually renders content inside the <noscript> tag (effectively ignoring <noscript> tag).

Root div is the place in HTML where we expect React to render implemented user interface.

Following source files (with explanation of details relevant for this topic) are an entry point of our React application:

src/index.js

// Imports React modules
import React from 'react';
import ReactDOM from 'react-dom/client';
import reportWebVitals from './reportWebVitals';

// Imports index.css for our SPA.
import './index.css';

// Imports our App component
import App from './App';

// Creating Root React Component / Node.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    // Strict Mode component provides us with React warnings in case of potential problems, such as
    // possible memory leaks. It performs this only when is run in Development environment.
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// Measure performance in Development environment.
reportWebVitals();

Looking at this file, we see instruction to fetch "root div" utilizing document.getElementById() method and that div is provided as input to ReactDOM.createRoot() method. From this point, React handles rendering "under the hood", meaning that in the rest of our source code we will not manipulate DOM directly (utilizing document object), but delegate actual DOM manipulations to React modules.

Root.render() method as an input takes two components: React.StrictMode and App. App is provided as the child of React.StrictMode component.

This HTML-like syntax is actually JSX. JSX is extension of Javascript syntax that looks similar to HTML (or HTML template language). It has full power of Javascript and practically every section written in JSX is actually preprocessed and executed as plain Javascript in the background. Instruction <App /> in HTML means nothing (it is invalid HTML tag), but in this case, it means "Render App Component" to this place.

In the src/App.js one can see App Component and JSX utilization to define its content.

src/App.js

// Importing svg React logo and App.css content
import logo from './logo.svg';
import './App.css';

// App component
function App() {

  // JSX to render on the interface
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
 </div>
  );
}

export default App;

In the implementation of App component, we can see interface that is actually rendered (React logo with link to "Learn React"). Term "className" utilized in JSX has the meaning of "class" in regular HTML. Since "class" is keyword in Javascript, that ambiguity had to be solved by defining another term for HTML class attribute.

Style of the page is defined in src/App.css.

src/App.css

.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

Rendering part of our application is covered through React components. Hierarchy of them is defined utilizing JSX, while actual rendering to the DOM is done by React.

Greeting App Home Page

Now, its time to change template React app provided to us to get "Greeting App" that we created in [Part 1] of this article using Express and HTML rendering concepts. To follow the requirement that we are trying to achieve, please check [Part 1] of this article.

First steps to perform are:

  • Remove 'src/index.css' file,
  • Remove line "import './index.css';" from src/index.js file,
  • Create Home Component with "Example Project Body" heading.
  • Render Home Component in App Component (instead of current React example).

After these changes, our source code looks as follows:

src/index.js

// Imports React modules
import React from 'react';
import ReactDOM from 'react-dom/client';
import reportWebVitals from './reportWebVitals';

// Imports our App component
import App from './App';

// Creating Root React Component / Node.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    // Strict Mode component provides us with React warnings in case of potential problems, such as
    // possible memory leaks.
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// Measure performance in Development environment.
reportWebVitals();

src/App.js

import './App.css';
import {Home} from "./greet/Home";

function App() {
  return (
    <Home />
  );
}

export default App;

src/App.css

body {
  padding: 50px;
  background: linear-gradient(to bottom right, #009ea7, #009ed7) fixed;
}

h1 {
  color: yellow;
  text-shadow: pink 2px 1px;
}

a {
  color: white;
  font-weight: bold;
}

form {
  height: 50px;
}

label {
  color: yellow;
}

input {
  border-radius: 10px;
  height: 25px;
}

button {
  height: 30px;
  border-radius: 10px;
}

.nav-separator::before {
  content: "|";
  margin-left: 5px;
  margin-right: 5px;
}

src/greet/Home.js

export function Home() {
    return (
      <> 
    <h1>Example Project Body</h1>
</>
    );
}

And the resulting home page:

Blog picture

Navigation

When we are in SPA scope, navigation between "pages" is done by SPA Framework and is done in browser. In comparison with server HTML rendering, this scenario does not involve basic HTML behavior for links and forms. As the reminder, basic HTML behavior is that on user's click to link, http request with new URL is sent to server and then based on server's response, interface is updated.

In React, it is preferred to use react-router-dom module. This module provides us with necessary implementation to incorporate routing in our React application.

So, first step is to install it as dependency to our application by executing the following command:

npm install --save react-router-dom

Since we expect router to choose which of our components are loaded (based on the URL), it makes sense that first rendered component in our App is React Router component to manage this choice. Naturally, we need to configure mapping between routes and actual components.

For this purpose, Default Greet component is created, and router configured to properly map individual routes to individual components (ones that we decided should act like pages).

src/greet/Greet.js

export function Greet() {
    return (
        <h1>Hello!</h1>
    );
}

src/App.js

import './App.css';
import {Home} from "./greet/Home";
import {createBrowserRouter, RouterProvider} from "react-router-dom";
import {Greet} from "./greet/Greet";

const router = createBrowserRouter(
    [
      {
        path: '/',
        element: <Home />,
      },
      {
        path: '/greet',
        element: <Greet />,
      }
    ]
);

function App() {
  return (
      <RouterProvider router={router} />
  );
}

export default App;

After these changes, when we access http://localhost:3000/ and http://localhost:3000/greet components Home and Greet are rendered.

Blog pictureBlog picture

To render navigation bar under main area where we present greeting message, NavBar component is created. Inside of that component, for links, we utilize Link component provided by React Router module. When it is ready, we use that component on every of our pages.

src/greet/NavBar.js

import {Link} from "react-router-dom";

export function NavBar() {
    return (
        <>
  <Link to={'/'}>Home</Link>
  <span className="nav-separator" />
  <Link to={'/greet'}>Default Greet</Link>
  <span className="nav-separator" />
</>
    );
}

src/greet/Home.js

import {NavBar} from "./NavBar";

export function Home() {
    return (
        <>
  <h1>Example Project Body</h1>
  <NavBar />
</>
    );
}

src/greet/Greet.js

import {NavBar} from "./NavBar";

export function Greet() {
    return (
        <>
  <h1>Hello!</h1>
  <NavBar />
</>
    );
}

These changes are resulting in rendered and functional navigation bar in the "Greet Application".

Blog pictureBlog picture

Accepting the Form Input

In Part 1, when form was submitted, request to the server was sent. Intention was that server builds new HTML to be presented in browser (with greeting message). In "Greeting App" case, we are still building the Form, and still submitting the name as the input. Difference is that given input will be processed in our application and not on the server.

To utilize this, we are improving Greet component to be able to accept name input from query parameters in URL. Additionally, we are creating AddName component, to define interface where our user will actually insert and submit its name.

src/greet/Greet.js

import {NavBar} from "./NavBar";
import {useLocation} from "react-router-dom";

export function Greet() {

    const location = useLocation();

    let name = new URLSearchParams(location.search).get('name');
    let message = 'Hello!';
    if (name) {
        message = `Hello ${name}!`;
    }

    return (
        <>
  <h1>{message}</h1>
  <NavBar />
</>
    );
}

src/greet/AddName.js

import {Form} from "react-router-dom";

export function AddName() {
    return (
        <>
  <h1>Add your name:</h1>
  <Form method="get" action="/greet">
    <label htmlFor="name">Name:</label>
    <input id="name" type="text" name="name" />
    <button type="submit">Submit</button>
  </Form>
</>
    );
}

Having AddName and Greet components ready to process user's input, only needed adaptations are in NavBar component and router configuration (defined in App.js).

src/App.js

import './App.css';
import {Home} from "./greet/Home";
import {createBrowserRouter, RouterProvider} from "react-router-dom";
import {Greet} from "./greet/Greet";
import {AddName} from "./greet/AddName";

const router = createBrowserRouter(
    [
        {
            path: '/',
            element: <Home/>,
        },
        {
            path: '/greet',
            element: <Greet/>,
        },
        {
            path: '/add-name',
            element: <AddName/>
        }
    ]
);

function App() {
  return (
      <RouterProvider router={router}/>
  );
}

export default App;

src/greet/NavBar.js

import {Link} from "react-router-dom";

export function NavBar() {
    return (
        <>
  <Link to={'/'}>Home</Link><span className="nav-separator" />
  <Link to={'/greet'}>Default Greet</Link><span className="nav-separator" />
  <Link to={'/add-name'}>Add Name</Link><span className="nav-separator" />
</>
    );
}

With this step, Greeting App is completely incorporated in React.

Blog pictureBlog picture

Get an Offer

Contact Us or Schedule a Meeting with us to get an offer for our development & consulting services regarding your current or next Web project.

There are no comments

Leave your comment