Nirmalya Ghosh I'm a computer science engineer specializing in web design and development with an eye for detail. I have over three years of experience with React and also fiddle with Ruby on Rails and Elixir.

JSX-driven components with Crank.js

5 min read 1677

JSX-driven Components With Crank.js

In this tutorial, we’ll introduce Crank.js by understanding what it is and why it was created. We’ll then put that knowledge to use by building a blogging application, which will use the JSONPlaceholder API.

What is Crank.js, and what problems does it solve?

Crank.js is a new framework for creating JSX-driven components with functions, promises, and generators. It’s heavily inspired by the React library.

While Vue and Svelte provided a solution for the problems that developers faced with React, they mainly used HTML template languages as the main way to use them. While there are other frameworks that use JSX (like Preact), they also followed React to convert from “a view layer” into “a UI runtime.”

Crank.js is a framework that validates the idea that “JSX-based components could be written not just with sync functions, but also with async functions, and with sync and async generator functions.” More about why Crank was created can be found here.

The code for the application built as a part of this tutorial is available on GitHub and is hosted on Vercel.

Installing Crank in our application

Let’s create the directory in which our application will reside:

mkdir introduction-to-crank.js && cd introduction-to-crank.js

Then initialize our application with npm:

npm init -y

This will create a package.json file with the following content:

Our Default package.json File
Our default package.json file.

Add Crank.js as a dependency in our project:

yarn add @bikeshaving/crank

And add Parcel as our bundler:

yarn add parcel-bundler --dev

The above command will add parcel as a dev dependency.

Now let’s create an i``ndex.html file with the following content:

// index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Introduction to Crank.js</title>
    <meta charset="UTF-8" />
  </head>
  <body>
    <script src="src/index.js"></script>
  </body>
</html>

Next, we’ll create an index.js file inside a new src directory with the following content:

// src/index.js

import { renderer } from "@bikeshaving/crank/dom";

renderer.render(<div id="hello">Hello world</div>, document.body);

And add the following two scripts in our package.json file:

// package.json

..
"scripts": {
  "dev": "parcel index.html",
  "build": "parcel build index.html",
  ..
},
..

Now, if we run yarn dev, we’ll get the following output:

We See An Error When Starting Our Dev Server
We seen an error when starting our dev server.

Let’s fix that in the next steps. Add babel-preset-crank to our list of dev dependencies:

yarn add babel-preset-crank --dev

And, finally, create a .babelrc file with the following content:

// .babelrc

{
  "presets": [
    "crank"
  ]
}

Now, if we start our development server, we’ll be able to view our app on http://localhost:1234/:

yarn dev
The Output From Starting Our Dev Server
The output from starting our dev server.

We should be able to view the following screen:

Our Basic Crank.js Application Setup
Our basic Crank.js application setup.

Adding Tailwind CSS to our application

To add some styles to our application, we’ll be using TailwindCSS. We can add Tailwind CSS to our application by adding the following line to our index.html file:

// index.html

<link href="https://unpkg.com/[email protected]^1.0/dist/tailwind.min.css" rel="stylesheet">

Please note that this isn’t the recommended way to add Tailwind CSS to a project. However, in the interest of keeping our tutorial short, we’re adding the CDN version of it. More information on how to properly install Tailwind CSS can be found in its docs.

Now we can use Tailwind in out project. To verify that, we can create a Navbar component inside src/components/navbar directory, which should contain the following:

// src/components/navbar/index.js

import { createElement } from "@bikeshaving/crank";

const Navbar = () => {
  return (
    <div className="bg-gray-100">
      <div className="max-w-2xl mx-auto p-8 flex justify-between">
        <div>
          <ul class="flex">
            <li class="mr-6">
              <a class="text-blue-500 hover:text-blue-800" href="#">
                Home
              </a>
            </li>
          </ul>
        </div>
        <div>
          <ul class="flex">
            <li class="mr-6">
              <a class="text-blue-500 hover:text-blue-800" href="#">
                About
              </a>
            </li>
            <li class="mr-6">
              <a class="text-blue-500 hover:text-blue-800" href="#">
                Contact
              </a>
            </li>
          </ul>
        </div>
      </div>
    </div>
  );
};

export default Navbar;

We can now import that component in our src/index.js file and use it:

// src/index.js

import { createElement } from "@bikeshaving/crank";
import { renderer } from "@bikeshaving/crank/dom";
import Navbar from "./components/navbar";

const App = () => {
  return (
    <div>
      <section>
        <Navbar />
      </section>
    </div>
  );
};

renderer.render(<App />, document.body);

Now, if we visit http://localhost:1234/, we should see the following screen:

Our Page Displaying The Navbar Component
Our page displaying the Navbar component.

So, using Tailwind CSS, we’ve created our first Crank.js component.

Fetching data from JSONPlaceholder

JSONPlaceholder is a free service that provides a fake online REST API for testing and prototyping. Using this service, we’ll be fetching a list of blog posts.

We’ll be showing posts in a separate component called Posts:

// src/components/posts/index.js

import { createElement } from "@bikeshaving/crank";

const Posts = async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts");
  const data = await response.json();

  return (
    <div className="bg-gray-100">
      <div className="max-w-2xl mx-auto p-8 flex justify-between">
        <div>
          <ul class="flex">
            <li class="mr-6">
              <a class="text-blue-500 hover:text-blue-800" href="#">
                Home
              </a>
            </li>
          </ul>
        </div>
        <div></div>
      </div>
    </div>
  );
};

export default Posts;

Let’s also import and add that component to our src/index.js file:

// src/index.js

const App = () => {
  return (
    <div>
      ..
      <section>
        <Posts />
      </section>
    </div>
  );
};

Now, if we visit http://localhost:1234/, we’ll see an error on our browser’s console:

index.js:3 Uncaught ReferenceError: regeneratorRuntime is not defined

More information regarding this issue can be found in this GitHub issue, but we can solve it by adding the regenerator-runtime package:

yarn add regenerator-runtime

Now we need to import that package in our src/index.js file:

// src/index.js

import "regenerator-runtime/runtime";
..

Now, if we visit http://localhost:1234/ again, we won’t see that issue any more.

Let’s show all the posts below the Navbar component. To do so, we need to update our Posts component with the following:

// src/components/posts/index.js

import { createElement } from "@bikeshaving/crank";

const Posts = async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts");
  const data = await response.json();

  return (
    <div className="bg-gray-100">
      <div className="max-w-2xl mx-auto p-8">
        {data.map((post) => {
          return (
            <div className="mb-16">
              <h2 className="font-bold text-xl mb-2">{post.title}</h2>
              <p className="font-light">{post.body}</p>
            </div>
          );
        })}
      </div>
    </div>
  );
};

export default Posts;

Now, if we visit http://localhost:1234/, we should be able to view all the posts.

Our Page Displaying The Posts Component
Our page displaying the Posts component.

Showing a loader while fetching a post

Up until now, we’re showing all the posts once all the API requests are complete. However, there could be an API request that might take some time to complete. As a result, we need to show a loader while the API request is pending. Let’s implement that next.

To do so, we’ll need to modify our src/components/posts/index.js file:

// src/components/posts/index.js

import { createElement, Fragment } from "@bikeshaving/crank";
import Spinner from "../spinner";
import Button from "../button";

const BlogPosts = async ({ postCount }) => {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${postCount}`
  );
  const post = await res.json();

  return (
    <Fragment>
      <h2 className="font-bold text-xl mb-2">{post.title}</h2>
      <p className="font-light">{post.body}</p>
    </Fragment>
  );
};

async function* PostsLoader({ isLoading, postCount }) {
  for await ({ isLoading, postCount } of this) {
    yield <Spinner />;
    yield <BlogPosts postCount={postCount} />;
  }
}

function* Posts() {
  let isLoading = false;
  let postCount = 1;

  this.addEventListener("click", (e) => {
    if (e.target.tagName === "BUTTON") {
      isLoading = !isLoading;
      postCount++;
      this.refresh();
    }
  });

  while (true) {
    yield (
      <div className="bg-white">
        <div className="max-w-2xl mx-auto p-8">
          <div className="mb-8">
            <PostsLoader isLoading={isLoading} postCount={postCount} />
          </div>
          <Fragment>
            <Button text={`Fetch post #${postCount + 1}`} />
          </Fragment>
        </div>
      </div>
    );
  }
}

export default Posts;

In the above code snippet, we’re fetching the first post by default and then incrementing the value of postCount by one. That count is incremented every time we click on the button. The API request to JSONPlaceholder is made, and it depends on the value of the postCount variable.

Crank.js uses generator components for fetching async components. More information regarding that can be found in its docs.

If we visit http://localhost:1234/ now, we should be able to see the following screen:

The Loader Is Shown Each Time We Fetch A New Post

We can now see that a loader is shown every time we fetch a new post.

Conclusion

In this tutorial, we’ve learned what Crank is and how we can build an application using it. The source code for the application built as a part of this tutorial is available on Github and is hosted on Vercel.

Personally, I feel that, while pretty new, it has some powerful features. Like the author of Crank, I also prefer the JSX syntax instead of HTML templates. However, I think Crank has a lot of potential if used in the proper use cases.

Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?

There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.

LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.

https://logrocket.com/signup/

LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Build confidently — .

Nirmalya Ghosh I'm a computer science engineer specializing in web design and development with an eye for detail. I have over three years of experience with React and also fiddle with Ruby on Rails and Elixir.

Leave a Reply