Typescript code

Typescript code.

Introduction

I’m about to start a new project in January 2023. I have a couple of weeks between projects so I thought that I will make a demonstration / learning application for myself regarding the technologies the project will use:

This blog post describes some experiences regarding the frontend development. I have previously written my backend experiences in the Javascript and Node Impressions blog post. I will later on write a third blog post regarding the serverless deployment using AWS lambda.

The demo app is in my Github repo: js-node-ts-react.

Webstore Demo App

I use the same Webstore application that I have used before, see Five Languages - Five Stories (well, there was also an implementation using Kotlin but I never bothered to update the blog post). I actually implemented the backend also using Javascript at that time, but now, I wanted to re-implement the demo-app using the Serverless local development environment and latest dependencies. I now also have more functional programming experience working with Clojure, so I wanted to see if this experience has had some influence in my Javascript programming. I previously implemented the frontend using Clojurescript and Reagent, and therefore it is interesting to compare my Clojurescript experience to Typescript / React when I implemented the frontend for this demonstration application.

Frontend

The frontend is a simple React application written using Typescript. I use Vite frontend development (bundling etc.). As a CSS utility library, I use Tailwind. I also use a couple of other libraries, e.g. React Router as a frontend routing library.

A screenshot of the demo app, Products view (products.tsx):

Products view

Products view.

Frontend Development

Here I list some lessons learned for myself when I implemented the frontend. Nothing new or special here - all these things are bread and butter for any Typescript / React developer. But I wanted to write down these lessons for myself so that I remember them better in my future Javascript projects.

Starter

I used akx/vite-react-ts-template for the frontend development. This template seemed to be a bit simpler than some other starter templates I evaluated. Install it using:

npx degit akx/vite-react-ts-template

React

I have used React previously with Clojurescript / Reagent - so I’m not a complete newbie with React.

But I had never used React with Javascript / Typescript. Therefore, before implementing this exercise, I read the excellent new beta React Docs. I strongly recommend browsing through that documentation and doing the exercises - the documentation and exercises create a strong mental model for you to understand React better.

React Router

Since I use React, using React Router as a frontend routing library is a natural choice.

Before implementing the routing for this demo application, I did the excellent React Routing Tutorial. After implementing the demo application I realized that most of the stuff in that tutorial is not needed in a simple frontend application like in the demo app I implemented. More about that in the next chapter.

Comparing SWR React Hook and React-router Loader Pattern

React-router provides API for fetching the data needed in the React component, see Loading Data chapter in the tutorial. This is an IoC (inverse of control, a.k.a. Hollywood principle): you provide a function for fetching the data and provide the function when configuring the router. Then the React component can get the loader using import { useLoaderData } from "react-router-dom"; API.

Compare the solutions:

When implementing the demo app, the evolution of fetching data was like this:

  1. I first started using Axios and React state.
export default function ProductGroups() {
  return productGroupsPage;
  const [productGroups, setProductGroups] = React.useState(null);

  React.useEffect(() => {
    axios
      .get(baseURL)
      .then((response) => {
        console.log("response", response);
        console.log("product_groups", response.data.product_groups);
        if (response.status === 200 && response.data.ret === "ok")
          setProductGroups(response.data.product_groups);
      })
      .catch((error) => {
        console.log("error", error);
      });
  }, []);

  return (
    <div className="App">
      <div>
        <Header />
        {productGroups && <ProductGroupsTable productGroups={productGroups} />}
      </div>
    </div>
  );
}
  1. Then based on the feedback given in the Koodiklinikka slack, I converted the React state / Axios using SWR React hook for fetching data (see: products.tsx).
export default function Products() {
  const { pgId } = useParams();
  const pgIdNum = parseInt(pgId || "-1");
  const productGroupsSWR = useSWR<ProductGroupsResponse>(productGroupsUrl, fetchJSON);
  const productGroups = productGroupsSWR.data?.product_groups;
  const pgName = productGroups?.find((pg) => pg.pgId === pgIdNum)?.name ||"";
  const title = "Products - " + pgName;
  const productsUrlWithPgId = productsUrl + `/${pgId}`;
  const productsSWR = useSWR<ProductsResponse>(productsUrlWithPgId, fetchJSON);
  const products = productsSWR.data?.products;

  return (
...
  1. Since the React-router tutorial used the loader pattern, I wanted to compare this solution to the SWR hook solution, and therefore I converted product.tsx to use the React-router loader pattern.
export async function productLoader({ params }: { params: productParams }): Promise<ProductType> {
  const { pgId, pId } = params;
  const productUrlWithIds = productUrl + `/${pgId}` + `/${pId}`;
  const product: ProductType = await axios
  .get(productUrlWithIds)
  .then((response) => {
    if (response.status === 200 && response.data.ret === "ok")
      return response.data.product;
  })
  .catch((error) => {
    console.log("error", error);
  });
  if (!product) {
    throw new Response("", {
      status: 404,
      statusText: "Not Found",
    });
  }
  return product;  
}

export function Product() {
  const product: ProductType = useLoaderData() as ProductType;
  const title = "Product";
...

I like the SWR react-hook solution (#2) best. Compared to solution #1, the SWR react-hook is simpler. Compared to solution #3, the SWR solution is more straightforward and no need for the IoC (inversion of control) pattern makes the solution more readable. (I couldn’t use SWR in the #3 solution since eslint complained that you cannot use a React hook in a non-React component - therefore using Axios again.)

But I’m not a frontend guru, so most probably there is some use case for the #3 solution.

Typescript

Programming Typescript with its type system makes frontend programming easier. E.g., many bugs related to parameters and function return values are detected in the source code with a good type system. Since I created the backend using Javascript and the frontend using Typescript I can now compare the two programming languages. My conclusion is that you should use Typescript both in the backend and in the frontend.

Vite

Vite provides various services for frontend development, e.g., hot reloading in the browser, and so on. I’m not going to dive deeper into Vite, you can read more about it in the Vite documentation.

Tailwind

See instructions in Get started with Tailwind CSS:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Then I followed: Install Tailwind CSS with Vite.

I have previously used Bulma, which is a CSS framework. Tailwind is a lower-level CSS library. If you need a coherent CSS framework with preconfigured components, go with Bulma. The project I’m about to start in one week uses Tailwind and therefore I wanted to have some Tailwind experience, and therefore I chose to use Tailwind in this learning project.

JSX vs Hiccup

In the Clojure land I used Hiccup to represent HTML. JSX does the same thing in the Javascript / Typescript land. Now that I have used both Hiccup and JSX I can say that Hiccup provides a better developer experience. Hiccup is just Clojure data structures (maps and vectors) and manipulating Hiccup using Clojure is really effective and pleasant.

Asynchronous Programming Model

The asynchronous programming model is something that you need to remember both on the backend and frontend sides. Example:

export default function ProductGroups() {
  const productGroupsSWR = useSWR(url, fetchJSON);
  const productGroups = productGroupsSWR.data?.product_groups;
...
          <ProductGroupsTable productGroups={productGroups} />
...

The productGroups are not there when we try to mount the ProductGroupsTable. Therefore we need to check if the data has arrived:

          {productGroups && (
            <ProductGroupsTable productGroups={productGroups} />
          )}

I was wondering about this and found out the problem using console.log in the ProductGroupsTable component.

Using React Off-the-shelf Components

I could have used a simple HTML Table, but I wanted to experiment with some React Off-the-shelf component, and therefore I used with ProductGroups and Products the TanStack Table headless table component. Using the examples it was quite simple to implement the tables used in this demo app with Tanstack Table. Tanstack Table provides pagination, sorting, filtering etc out of the box.

Though, you could implement those features yourself. Aarni Koskela demonstrated in the Koodiklinikka slack how he implemented pagination in a few minutes. (Quite an impressive video, you can watch it also on Youtube.)

Error Handling

I skipped error handling in this demo app. Possibly I implement later on some basic error handling.

Thanks

I got wonderful help from specialists who are active in the Finnish Koodiklinikka Slack, in channels #javascript, #typescript and #react. I have mentioned some names in the project README with their permission. If you are a Finnish developer, I really recommend using the excellent Koodiklinikka Slack if you want to learn new programming languages or technologies - there are a lot of competent experts in the Koodiklinikka Slack willing to help each other.

Conclusions

Typescript is a good functional programming language with a coherent type system. Regarding my backend and frontend experiences with this demo app, you should use Typescript both in the backend and frontend sides.

React is an established frontend framework with a great ecosystem. I have no experience e.g. using Vue or Svelte, so I cannot compare React to those frameworks. But for a rather mediocre frontend developer like myself using React is quite simple and you can be quite productive with React just by reading and doing a couple of React tutorials.

I have more experience implementing frontends using Clojurescript and its excellent React wrapper, Reagent, and Hiccup. I think Clojurescript provides a better developer experience but I also understand that using Clojure/Clojurescript requires some learning curve, and using main stream languages like Typescript, it is easier to get a job as a frontend developer.

The writer is working at a major international IT corporation building cloud infrastructures and implementing applications on top of those infrastructures.

Kari Marttila

Kari Marttila’s Home Page in LinkedIn: https://www.linkedin.com/in/karimarttila/