SolidJS Crash Course - Building a REST API Client - Part 1
Tags: solidjs, webdev, javascript, typescript
Published: 2023-01-01 13:14:15

## Introduction

While React, Angular and VueJS are already famous within the Web Development community. SolidJS is the new cool framework in town. And everyone is hyped about it. And in this series of articles, you’re gonna learn the ins and outs of Solid JS and its reactivity. There are lot of concepts to cover, so buckle up! 

If you'd rather want to watch a video, I've got you 🙌

{% youtube L1f30LR5C_o %}

## Setting up the project
We're going to use [pnpm](https://pnpm.io/installation) to install the packages. You can use npm or yarn as well. You can install pnpm by running `npm install -g pnpm` in your terminal.

### Creating and Serving
```shell
npx degit solidjs/templates/ts solid-rest-client-app
cd solid-rest-client-app
pnpm i # or `npm install` or `yarn`
pnpm run dev # Open the app on localhost:3000
# OR
# npm run dev
# yarn dev
```

Once you have run the last command, you should be able to see the app running at [http://localhost:3000](http://localhost:3000)

### Adding Tailwind CSS
Follow the instructions from [SolidJS Official Docs](https://tailwindcss.com/docs/guides/solidjs) OR from the instructions below.

Install the packages using the following command:
```shell
pnpm add --save-dev tailwindcss postcss autoprefixer
```

Then run the following command to generate the tailwind config
```shell
npx tailwindcss init -p
```

Replace the `content` property to target the SolidJS files in the `tailwind.config.js` file:
```js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

Now add the following CSS in the `src/index.css` file:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Existing CSS Code */
```

### Add Nunito Font
Add the Nunito font in the `index.html` inside the `<head></head>` tags:

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700&display=swap" rel="stylesheet">
```

Finally, add the Nunito font to the index.css file as follows:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  margin: 0;
  font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

```

## Add Ionicons
Add the [Ionic Fonts (Ionicons)](https://ionic.io/ionicons) in the `index.html` inside the `<head></head>` tags as follows:
```html
<script type="module" src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"></script>
```

Let's try using an icon now. Update the App.tsx file to add an Ionicon as follows:
```jsx
const App: Component = () => {
  return (
    <div class={styles.App}>
      <header class={styles.header}>
        <img src={logo} class={styles.logo} alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          class={styles.link}
          href="https://github.com/solidjs/solid"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn Solid
        </a>
        <ion-icon name="accessibility-outline"></ion-icon>
      </header>
    </div>
  );
};
```

You'll notice that TypeScript isn't happy at all at this moment and may show something like this:


![IonIcons making TS Sad](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hrkazh35at1u1ttk1a8s.png)


This is because SolidJS and TypeScript together do not understand what an ‘ion-icon’ element is. Because it is being used in JSX. Let's fix it by providing the types. Create a new folder named `types` inside the `src` folder. And a new file inside named `solid-js.d.ts`. Then add the following code inside:

```ts
import "solid-js";

declare module "solid-js" {
  namespace JSX {
    interface IntrinsicElements {
      "ion-icon": any;
    }
  }
}
```

After this, you will see that the App.tsx and TypeScript are happy now. Look at the [app](http://localhost:3000) and you should be able to see the accessibility icon on the view.

## Add ESLint
Coding is great. But writing good code is better. We'll add ESlint to our project to make sure our code stays great throughout the development of the app. Sorry the Software Architect inside me can't refrain from enforcing standards 😄!

Run the following command from the terminal to set up ESLint:
```shell
npx eslint --init
```
Select the following options when presented throughout the installation:
- `To check syntax, find problems, and enforce code style`
- `JavaScript modules (import/export)`
- `None of these` (for Framework)
- `Yes` (for "Does your project use TypeScript")
- `Browser` (for "Where does your code run")
- `Standard: https://github.com/standard/eslint-config-standard-with-typescript` (for "Which style guide do you want to follow")
- `JavaScript` (for "What format do you want your config file to be in")
- `JSON` for the type of file to use
- `pnpm` to install the packages


Then run the following to install the required packages:
```shell
pnpm add --save-dev eslint eslint-plugin-solid @typescript-eslint/parser 
```

Once done, update the `.eslintrc` file to use the following code:
```json
{
  "env": {
    "browser": true,
    "es2021": true
  },
  "overrides": [],
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "rules": {},
  "parser": "@typescript-eslint/parser",
  "plugins": ["solid"],
  "extends": [
    "eslint:recommended",
    "standard-with-typescript",
    "plugin:solid/typescript"
  ]
}
```

## Navigation and Routing
We'll start creating some pages and will set up routing. To work with routes, we will install the [solid-app-router](https://www.npmjs.com/package/solid-app-router) package by running the following command in the terminal:
```shell
pnpm add --save solid-app-router
```

### Creating the Navbar and adding basic routes
Create a new folder named `components` inside the `src` folder, then create a new file named `Navbar.tsx` inside the `src/components` folder. Add the following code in the file:

```jsx
import { Component } from 'solid-js';
import { Link } from 'solid-app-router';

const Navbar: Component = () => {
  return (
    <header class="bg-purple-600 text-white py-2 px-8 h-16 flex items-center justify-between">
      <Link class="hover:opacity-50 hero" href='/'>REST in Peace</Link>
      <div class="flex items-center gap-4">
        <Link class="hover:opacity-50" href='/about'>About</Link>
      </div>
    </header>
  )
}

export default Navbar;
```

Now add the following Route configuration in the App.tsx file as follows:
```jsx
import type { Component } from 'solid-js';
import styles from './App.module.css';
import { hashIntegration, Route, Router, Routes } from 'solid-app-router';
import Navbar from './components/Navbar';

const App: Component = () => {
  return (
    <Router source={hashIntegration()}>
      <div class={styles.App}>
        <Navbar />
      </div>
      <Routes>
        <Route path="/" component={() => <div>Home Component</div>}></Route>
        <Route path="/about" component={() => <div>About Component</div>}></Route>
      </Routes>
    </Router>
  );
};

export default App;

```

If you look at the app now, you should be able to see the Navbar with the logo and about link. You can click on both to navigate to the different routes. 


![Navbar with links](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/el8wp0lall6xmb7oulk6.png
)

Notice that we're using the `<Router>` component from `solid-app-router` to wrap all the routing configuration. Inside the `Router` we have both the `Navbar` component and the `<Routes>` component having individual `<Route>` elements. Each `<Route>` element defines how the route would work. Following are the possible combinations of props you can provide:
```ts
export declare type RouteProps = {
    path: string | string[];
    children?: JSX.Element;
    data?: RouteDataFunc;
} & ({
    element?: never;
    component: Component;
} | {
    component?: never;
    element?: JSX.Element;
    preload?: () => void;
});
```

Notice that `path` is required for each route. All other properties are optional. But we can't provide both `element` and `component` on the same route. Notice there's an OR operator between the last two conditions being in a bracket:
```ts
({
    element?: never;
    component: Component;
} | {
    component?: never;
    element?: JSX.Element;
    preload?: () => void;
})
```

So for now, we're providing hardcoded functions returning JSX as the `component` prop for both routes. We'll switch to using `element` later in this tutorial.

## Creating Home and About Components:
Let's create both the Home page and the About page properly now. Create a new folder named `pages` in the `src` folder.

### Home Component:
Create a new file inside the `pages` folder named `Home.tsx` and paste the following code in it:

```tsx
import { Outlet } from 'solid-app-router';
import { Component } from 'solid-js';

const Home: Component = () => {
  return (
    <div class="flex flex-col md:flex-row gap-4 h-full flex-1">
      <div class="w-full md:w-1/4 bg-gray-200 min-h-full border-gray-300 border p-4 rounded-lg">
        <div class="flex justify-between py-4">
          <h1 class="text-sm ">Rest Requests</h1>
          <button class="flex hover:bg-opacity-60 justify-center items-center p-4 bg-purple-600 rounded-full text-white w-8 h-8" onClick={() => alert('To be implemented')}>
            <div>+</div>
          </button>
        </div>
      </div>
      <div class="flex-1 min-h-full">
        <Outlet />
      </div>
    </div>
  )
}

export default Home;
```

### About Component:
Create a new file named `About.tsx` inside the `pages` folder. Then paste the following code in it:
```tsx
import { Component } from 'solid-js';

const About: Component = () => {
  return (
    <div>
      <h2>This is About</h2>
    </div>
  )
}

export default About;
```

Now that we have created both the files, let's update the `App.tsx` file's `<Router>` element to use the routes as follows:

```tsx
import type { Component } from 'solid-js';
import { hashIntegration, Route, Router, Routes } from 'solid-app-router';
import Navbar from './components/Navbar';
import About from './pages/About'; // <!-- new import
import Home from './pages/Home'; // <!-- new import

const App: Component = () => {
  return (
    <Router source={hashIntegration()}>
      <div class="flex flex-col h-full min-h-screen">
        <Navbar></Navbar>
        <main class="px-8 py-4 flex-1 flex flex-col h-full">
          <Routes>
            <Route path="/about" element={<About />} />
            <Route path="/" element={<Home />}>
              {/* <Route path="/" element={<RestClientIndex />} />
              <Route
                path="/:id"
                element={<RestClient />}
                data={fetchSelectedRequest}
              /> */}
            </Route>
          </Routes>
        </main>
      </div>
    </Router>
  );
};

export default App;

```

Now if you go to the Home page, you'll see the following:

![Real Components](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h9tossjv4239p2d5ki1m.png)

## Creating the Interfaces
We're using TypeScript... and it it super cool. And we're going to work with some Rest API Requests in this app. So we'll create the interfaces required to work with them. 
Create a new folder named `interfaces` inside the `src` folder. And then create a file inside named `rest.interfaces.ts`.
Finally, add the following code inside the file:

```ts
interface IRequest {
  headers?: {
    [key: string]: string;
  }[];
  method: string;
  url: string;
  body?: any;
}
export interface IRestRequest {
  id: string;
  name: string;
  description: string;
  request: IRequest;
}

export interface IRestResponse {
  data: any;
  status: number;
  headers: any;
}
```

Notice that we're only exporting two interfaces from this file. The `IRestRequest` and the `IRestResponse` interfaces. They represent a Request and Response in the context of our app. Notice that `IRestRequest` internally uses the `IRequest` interface for the `request` property. This `request` property is the actual request we'll be passing to `axios` in a later stage in this tutorial.

## Creating Requests Data
Let's start creating some data. We'll create some dummy requests to work with. Update the Home.tsx to add the following requests array:
```jsx
import { Outlet } from 'solid-app-router';
import { Component } from 'solid-js';
import { IRestRequest } from '../interfaces/rest.interfaces';

const Home: Component = () => {
  const requests: IRestRequest[] = [
    {
      id: "1",
      name: "Get Scores",
      description: "Getting scores from server",
      request: {
        method: "GET",
        url: "https://scorer-pro3.p.rapidapi.com/score/game123",
        headers: [
          {
            key: "X-RapidAPI-Host",
            value: "API_HOST_FROM_RAPID_API",
          },
          {
            key: "X-RapidAPI-Key",
            value: "API_KEY_FROM_RAPID_API",
          },
        ],
      },
    },
    {
      id: "2",
      name: "Add Score",
      description: "Adding scores to server",
      request: {
        method: "POST",
        url: "https://scorer-pro3.p.rapidapi.com/score",
        headers: [
          {
            key: "X-RapidAPI-Host",
            value: "API_HOST_FROM_RAPID_API",
          },
          {
            key: "X-RapidAPI-Key",
            value: "API_KEY_FROM_RAPID_API",
          },
        ],
        body: JSON.stringify({
          score: 100,
          gameId: "123",
          userId: "test123",
        }),
      },
    },
  ];

  return (
    <div class="flex flex-col md:flex-row gap-4 h-full flex-1">
      <!-- further code here -->
    </div>
  )
}

export default Home;
```

Note that we have the data according to our interfaces provided. Also note that each request has this `headers` property. We're not going to use this in this tutorial. This is just to give you an idea on how you can extend this app to work with Request Headers as well on your own. Or wait for another tutorial by me 🙂!

## Creating Request elements
Now that we have the data, let's use that within the same `Home.tsx` file by using the `For` element from `solid-js` to loop over the requests array and to render some requests.
Update the template in the `Home.tsx` file as follows:

```tsx
import { Link, Outlet } from "solid-app-router";
import { Component, For } from "solid-js";
import { IRestRequest } from "../interfaces/rest.interfaces";

const Home: Component = () => {
  {/*  ... */}
  return (
    <div class="flex flex-col md:flex-row gap-4 h-full flex-1">
      <div class="w-full md:w-1/4 bg-gray-200 min-h-full border-gray-300 border p-4 rounded-lg">
        <div class="flex justify-between py-4">
          <h1 class="text-sm ">Rest Requests</h1>
          <button
            class="flex hover:bg-opacity-60 justify-center items-center p-4 bg-purple-600 rounded-full text-white w-8 h-8"
            onClick={() => alert("To be implemented")}
          >
            <div>+</div>
          </button>
        </div>
        {/*  👇 We've added this 👇 */}
        <div class="list">
          <For each={requests} fallback={<div>Loading...</div>}>
            {(item) => (
              <Link href={`/${item.id}`} class="relative list__item">
                <div
                  class="p-2 hover:bg-gray-300 cursor-pointer pr-12 rounded-lg mb-2"
                  classList={{
                    "list__item--active": Boolean(
                      location.pathname === `/${item.id}`
                    ),
                  }}
                >
                  <div>{item.name}</div>
                  <div class="text-xs break-all">
                    {item.request.method} {item.request.url}
                  </div>
                </div>
              </Link>
            )}
          </For>
        </div>
      </div>
      <div class="flex-1 min-h-full">
        <Outlet />
      </div>
    </div>
  );
};

export default Home;

```


![Rendering requests list](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ax0i71hc0jldl6l48el.png)



As you can see, each item being rendered is a `<Link>` element from `solid-app-router`. Clicking a request should take you to the details page of the request. And we've not implemented it yet. Each item shows the name, the method and the URL of the request on the view.

## Creating a Reactive-Persistent Store using Signals
Since we want our store to be persistent, we'll use the `@solid-primitives/storage` package to save all the requests in the browser. We'll initiate the store with the hard-coded/dummy data if it is empty. Let's install the package first as follows:

```shell
pnpm add --save @solid-primitives/storage
```

Now create a new file named `store.ts` inside the `src` folder and paste the following code inside:

```ts
import { IRestRequest } from "./interfaces/rest.interfaces";
import { createStorageSignal } from "@solid-primitives/storage";

export const [restRequests, setRestRequests] = createStorageSignal<
  IRestRequest[]
>(
  "requests",
  [
    {
      id: "1",
      name: "Get Scores",
      description: "Getting scores from server",
      request: {
        method: "GET",
        url: "https://scorer-pro3.p.rapidapi.com/score/game123",
        headers: [
          {
            key: "X-RapidAPI-Host",
            value: "API_HOST_FROM_RAPID_API",
          },
          {
            key: "X-RapidAPI-Key",
            value: "API_KEY_FROM_RAPID_API",
          },
        ],
      },
    },
    {
      id: "2",
      name: "Add Score",
      description: "Adding scores to server",
      request: {
        method: "POST",
        url: "https://scorer-pro3.p.rapidapi.com/score",
        headers: [
          {
            key: "X-RapidAPI-Host",
            value: "API_HOST_FROM_RAPID_API",
          },
          {
            key: "X-RapidAPI-Key",
            value: "API_KEY_FROM_RAPID_API",
          },
        ],
        body: JSON.stringify({
          score: 100,
          gameId: "123",
          userId: "test123",
        }),
      },
    },
  ],
  {
    deserializer: (val): IRestRequest[] => {
      if (val === null) {
        return [];
      }
      return JSON.parse(val);
    },
    serializer: (val) => {
      return JSON.stringify(val);
    },
  }
);

```

Note that we're using the `createStorageSignal` method with the type `IRestRequest[]` to create a storage signal to store an array of our requests. Notice that the first argument of this method has the value `"requests"` which sets the name of this store both for the app's session as well as the key for the browser's storage (`localStorage`). The second parameter is the initial value of the store and we're providing our hard-coded/dummy data here.
The third parameter is the storage options. We're passing the methods `deserializer` and `serializer` which are responsible for transforming data when reading from the storage and when writing to the storage respectively. 
Finally, we're exporting both the `restRequests` which is a Solid JS accessor to access value from a signal, and the `setRestRequests` method to update the value of the signal.

Now that we have the storage created, let's use it in the `Home.tsx` as follows:

```tsx
import { Link, Outlet } from "solid-app-router";
import { Component, For } from "solid-js";
import { restRequests } from "../store"; // <-- importing accessor

const Home: Component = () => {
  return (
    <div class="flex flex-col md:flex-row gap-4 h-full flex-1">
      <div class="w-full md:w-1/4 bg-gray-200 min-h-full border-gray-300 border p-4 rounded-lg">
        <div class="flex justify-between py-4">
          <h1 class="text-sm ">Rest Requests</h1>
          <button
            class="flex hover:bg-opacity-60 justify-center items-center p-4 bg-purple-600 rounded-full text-white w-8 h-8"
            onClick={() => alert("To be implemented")}
          >
            <div>+</div>
          </button>
        </div>
        <div class="list">
          {/*  Using the accessor here */}
          <For each={restRequests()} fallback={<div>Loading...</div>}>
            {(item) => (
              <Link href={`/${item.id}`} class="relative list__item">
                <div
                  class="p-2 hover:bg-gray-300 cursor-pointer pr-12 rounded-lg mb-2"
                  classList={{
                    "list__item--active": Boolean(
                      location.pathname === `/${item.id}`
                    ),
                  }}
                >
                  <div>{item.name}</div>
                  <div class="text-xs break-all">
                    {item.request.method} {item.request.url}
                  </div>
                </div>
              </Link>
            )}
          </For>
        </div>
      </div>
      <div class="flex-1 min-h-full">
        <Outlet />
      </div>
    </div>
  );
};

export default Home;

```

Note that we've removed the hardcoded requests from the `Home.tsx` from the file as we've moved that to the `store.ts` file. The Home component looks so clean now 🫧 You can see the persistent requests in the localStorage as follows in the Chrome Debugger:


![Requests Persistent Storage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ffr5vzk6tyqien7mls89.png)


## Creating the Add Request Modal
Since we have the storage implemented now, let's create a way to add/create more requests. This is where the fun begins because we're now starting to add things dynamically to the app. We'll create a modal to add a new request. To do that, create a new file named `RequestModal.tsx` inside the `src/components` folder. Add the following code to create a basic modal:

```tsx
import { Component, ComponentProps, Show } from "solid-js";
import { IRestRequest } from "../interfaces/rest.interfaces";

interface RequestModalProps extends ComponentProps<any> {
  show: boolean;
  onModalHide: (id: string | null) => void;
  request?: IRestRequest;
}

const RequestModal: Component<RequestModalProps> = (
  props: RequestModalProps
) => {
  return (
    <Show when={props.show}>
      <div class="fixed z-50 top-0 left-0 right-0 bottom-0 bg-[rgba(0,0,0,0.75)]">
        <div
          class="relative max-h-[85%] overflow-y-auto top-20 bg-gray-200 max-w-md m-auto h- block p-8 pb-8 border-t-4 border-purple-600 rounded-sm shadow-xl"
        >
          <h5 class="text-4xl font-bold mb-4">
            {(props.request ? "Edit" : "Create") + " Request"}
          </h5>
          <span class="absolute bottom-9 right-8">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              class="w-10 h-10 text-purple-600"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
            >
              <path
                stroke-linecap="round"
                stroke-linejoin="round"
                stroke-width="2"
                d="M13 10V3L4 14h7v7l9-11h-7z"
              />
            </svg>
          </span>
        </div>
      </div>
    </Show>
  );
};

export default RequestModal;
```

Note that the `RequestModal` uses the `<Show>` component from Solid JS that shows the template/components within it based on the condition supplied to the `when` attribute. We're relying on the `show` prop of the `RequestModal` to tell the `<Show>` component when to display stuff. This is so that we can control the `RequestModal` from outside the component to make it visible or hidden.
Now that we have this component, let's add it in the `Home.tsx` as follows:

```tsx
import { Link, Outlet, useLocation, useNavigate } from "solid-app-router";
import { Component, createSignal, For } from "solid-js";
import RequestModal from "../components/RequestModal";
import { restRequests } from "../store";

const Home: Component = () => {
  const [showModal, setShowModal] = createSignal(false);
  const navigate = useNavigate();
  const location = useLocation();
  return (
    <div class="flex flex-col md:flex-row gap-4 h-full flex-1">
      <div>
        <button onClick={() => setShowModal(!showModal())}>Click Me</button>
        <RequestModal
          show={showModal()}
          onModalHide={(id: string | null) => {
            setShowModal(!showModal());
          }}
        />
      </div>
      <div class="w-full md:w-1/4 bg-gray-200 min-h-full border-gray-300 border p-4 rounded-lg">
        <div class="flex justify-between py-4">
          <h1 class="text-sm ">Rest Requests</h1>
          <button
            class="flex hover:bg-opacity-60 justify-center items-center p-4 bg-purple-600 rounded-full text-white w-8 h-8"
            onClick={() => alert("To be implemented")}
          >
            <div>+</div>
          </button>
        </div>

        <div class="list">
          <For each={restRequests()} fallback={<div>Loading...</div>}>
            {(item) => (
              <Link href={`/${item.id}`} class="relative list__item">
                <div
                  class="p-2 hover:bg-gray-300 cursor-pointer pr-12 rounded-lg mb-2"
                  classList={{
                    "list__item--active": Boolean(
                      location.pathname === `/${item.id}`
                    ),
                  }}
                >
                  <div>{item.name}</div>
                  <div class="text-xs break-all">
                    {item.request.method} {item.request.url}
                  </div>
                </div>
              </Link>
            )}
          </For>
        </div>
      </div>
      <div class="flex-1 min-h-full">
        <Outlet />
      </div>
    </div>
  );
};

export default Home;

```

Let's look at what we've done in a few steps apart from the changes in the imports at the top. 

```tsx
const Home: Component = () => {
  const [showModal, setShowModal] = createSignal(false);
  const navigate = useNavigate();
  const location = useLocation();
  // more code
};

export default Home;
```

We've created a signal `showModal` that we'll use to show or hide the modal. I.e. we'll pass this as the `show` prop of the `RequestModal` later. We also create two constants. Once for navigation and one for accessing location. 

```html
<div>
  <button onClick={() => setShowModal(!showModal())}>Click Me</button>
  <RequestModal
    show={showModal()}
    onModalHide={(id: string | null) => {
      setShowModal(!showModal());
    }}
  />
</div>
```
In the above code, we have a button that toggles the value of `showModal` signal. And we have the `RequestModal` being used which has the value of `showModal` assigned to the `show` prop. You can see we also provide a function for the `onModalHide` prop. This will be called when then `RequestModal` is hidden. We haven't written the logic of it yet.

With all this, you should be able to see this in the app when you click the `Click Me` text on the left. 


![Create Request Simple Modal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jn9o1baqoexb5n5nhqae.png)

Notice that we're not able to close the modal yet. We'll work on it in a bit.


## Creating the `IconButton` component
We are going to use some icon buttons throughout the application. So it is a good idea to create a reusable `IconButton` component. Create a new file in the `src/components` folder named `IconButton.tsx`. Then add the following code to it:

```tsx
import { Component, ComponentProps } from "solid-js";

interface IconButtonProps extends ComponentProps<any> {
  onClick: (event: MouseEvent) => void;
  label: string;
  icon: string;
  type?: "reset" | "submit" | "button";
}

const IconButton: Component<IconButtonProps> = ({
  onClick,
  label,
  icon,
  type,
}) => {
  return (
    <button
      onclick={onClick}
      role="button"
      type={type || "button"}
      title={label}
      class="w-6 h-6 flex transition-all ease-in-out duration-100 hover:scale-125 items-center justify-center text-white bg-purple-600 border border-purple-600 rounded-full hover:bg-purple-700 active:text-white focus:outline-none focus:ring"
    >
      <span class="sr-only">{label}</span>
      <ion-icon name={icon}></ion-icon>
    </button>
  );
};

export default IconButton;
```

This component is quite simple. We're styling a button in here and using an `<ion-icon>` element to show the target icon. Look at the `IconButtonProps` interface to see what possible props we can pass to this component.

Let's replace the "Add Request" button in the `Home.tsx` file with this new `IconButton` component. We'll also remove the "Click Me" button from the template as well:

```tsx
import { Link, Outlet, useLocation, useNavigate } from "solid-app-router";
import { Component, createSignal, For } from "solid-js";
import IconButton from "../components/IconButton";
import RequestModal from "../components/RequestModal";
import { restRequests } from "../store";

const Home: Component = () => {
  const [showModal, setShowModal] = createSignal(false);
  const navigate = useNavigate();
  const location = useLocation();
  return (
    <div class="flex flex-col md:flex-row gap-4 h-full flex-1">
      <div>
        <RequestModal
          show={showModal()}
          onModalHide={(id: string | null) => {
            setShowModal(!showModal());
          }}
        />
      </div>
      <div class="w-full md:w-1/4 bg-gray-200 min-h-full border-gray-300 border p-4 rounded-lg">
        <div class="flex justify-between py-4">
          <h1 class="text-sm ">Rest Requests</h1>
          {/* Replaced the Add Request Button with IconButton */}
          <IconButton
            onClick={() => setShowModal(true)}
            icon="add"
            label="Add Request"
          />
        </div>

        <div class="list">
          <For each={restRequests()} fallback={<div>Loading...</div>}>
            {(item) => (
              <Link href={`/${item.id}`} class="relative list__item">
                <div
                  class="p-2 hover:bg-gray-300 cursor-pointer pr-12 rounded-lg mb-2"
                  classList={{
                    "list__item--active": Boolean(
                      location.pathname === `/${item.id}`
                    ),
                  }}
                >
                  <div>{item.name}</div>
                  <div class="text-xs break-all">
                    {item.request.method} {item.request.url}
                  </div>
                </div>
              </Link>
            )}
          </For>
        </div>
      </div>
      <div class="flex-1 min-h-full">
        <Outlet />
      </div>
    </div>
  );
};

export default Home;

```

Great! Now let's move towards creating the Add Request form.

## Using Solid JS Forms to create requests
We're going to use the `solid-forms` package to work with forms in this tutorial. I've seen multiple forms libraries but found this one pretty nice. Install the package as follows:

```shell
pnpm add --save solid-forms
```

### Implementing the TextField component
Now create a new file under `src/components` folder and name it `TextField.tsx`. This component is going to be used whenever we want to show an Input or a TextArea component. Add the following code into the created file:

```tsx
import { IFormControl } from "solid-forms";
import { Component } from "solid-js";

export const TextField: Component<{
  control: IFormControl<string>;
  label: string;
  placeholder?: string;
  type?: string;
  rows?: number;
  id: string;
  class?: string;
  valueUpdated?: (val: any) => void;
}> = (props) => {
  const type = props.type || "text";
  const onInput = (e: { currentTarget: { value: string } }) => {
    props.control.markDirty(true);
    props.control.setValue(e.currentTarget.value);
  };

  const onBlur = () => {
    props.control.markTouched(true);
    if (props.valueUpdated) {
      props.valueUpdated(props.control.value);
    }
  };
  return (
    <>
      <label class="sr-only" for={props.id}>
        {props.label}
      </label>
      {type === "textarea" ? (
        <textarea
          value={props.control.value}
          rows={props.rows || 3}
          oninput={onInput}
          onblur={onBlur}
          placeholder={props.placeholder}
          required={props.control.isRequired}
          id={props.id}
          class={`w-full p-3 text-sm border-gray-200 rounded-lg ${props.class}`}
        />
      ) : (
        <input
          type="text"
          value={props.control.value}
          oninput={onInput}
          onblur={onBlur}
          placeholder={props.placeholder}
          required={props.control.isRequired}
          id={props.id}
          class={`w-full p-3 text-sm border-gray-200 rounded-lg ${props.class}`}
        />
      )}
    </>
  );
};

```

Apart from some obvious props, the important implementation here to consider is the `onblur` and `oninput` methods used on both the `<input>` and `<textarea>` elements in the template. The `TextField` accepts a `control` prop which is supposed to be a Form Control from Solid Forms. Whenever there is an input **change** in the `<input>` or `<textarea>` element, the `onInput` method is triggered and the value of the control is set via the `props.control.setValue` statement.
Similarly, we let the parent component know that the value has been changed using the `valueUpdated` prop. Whenever we **blur** from either the Input or the TextArea, we call the `props.valueUpdated` method if it is provided (since it is an optional prop).

### Creating the RestClientForm
This form is going to be used for both creating and editing a request. And it is going to be heavily based on multiple `TextField` component. Create a new file named `RestClientForm.tsx` in the `src/components` folder. Then add the following code to it:

```tsx
import { Component } from "solid-js";
import { IRestRequest } from "../interfaces/rest.interfaces";

export const RestClientForm: Component<{
  request?: Partial<IRestRequest>;
  formSubmit: Function;
  formUpdate?: Function;
  actionBtnText: string;
}> = (props) => {
  return (
    <form
      action=""
      class="space-y-4"
      classList={{}}
      onSubmit={(e) => {
        e.preventDefault();
      }}
    >
      <div class="grid grid-cols-1 gap-4">
        <div>
          <label for="name" class="mb-4 block">
            Name
          </label>
          <input placeholder="name" />
        </div>
        <div>
          <label for="url" class="mb-4 block">
            URL
          </label>
          <input placeholder="url" />
        </div>

        <div>
          <label class="my-4 block">Method</label>
          <input placeholder="method" />
        </div>
      </div>
      <div>
        <label class="my-4 block">Body</label>
        <input placeholder="body" />
      </div>

      <div class="mt-4">
        <button
          disabled={false}
          type="submit"
          class="inline-flex items-center disabled:bg-gray-500 justify-center w-full px-5 py-3 text-white bg-purple-600 hover:bg-purple-700 rounded-lg sm:w-auto"
        >
          <span class="font-medium"> {props.actionBtnText} </span>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            class="w-5 h-5 ml-3"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="2"
              d="M14 5l7 7m0 0l-7 7m7-7H3"
            />
          </svg>
        </button>
      </div>
    </form>
  );
};

```
This components accepts a function against the `formSubmit` prop which is going to be called upon...well... on the form submit 😄! Another mandatory prop is the `actionBtnText` so we can show the desired label on the submit button. 
If you have a look at the above code closely, you're going to notice that we're not using any `TextField` components so far 🤷‍♂️. Well, hold your horses. For now, let's use this `RestClientForm` component in the `RequestModal` component and see how everything looks. Add the `RestClientForm` in the `RequestModal.tsx` file as follows:

```tsx
import { Component, ComponentProps, Show } from "solid-js";
import { IRestRequest } from "../interfaces/rest.interfaces";
import { setRestRequests, restRequests } from "../store";
import { RestClientForm } from "./RestClientForm";

interface RequestModalProps extends ComponentProps<any> {
  show: boolean;
  onModalHide: (id: string | null) => void;
  request?: IRestRequest;
}

const RequestModal: Component<RequestModalProps> = (
  props: RequestModalProps
) => {
  return (
    <Show when={props.show}>
      <div class="fixed z-50 top-0 left-0 right-0 bottom-0 bg-[rgba(0,0,0,0.75)]">
        <div class="relative max-h-[85%] overflow-y-auto top-20 bg-gray-200 max-w-md m-auto h- block p-8 pb-8 border-t-4 border-purple-600 rounded-sm shadow-xl">
          <h5 class="text-4xl font-bold mb-4">
            {(props.request ? "Edit" : "Create") + " Request"}
          </h5>
          <RestClientForm
            formSubmit={(request: IRestRequest) => {
              const id = self.crypto?.randomUUID() || Date.now().toString();
              setRestRequests([
                ...(restRequests() || []),
                {
                  ...request,
                  id,
                },
              ]);
              props.onModalHide(id);
            }}
            actionBtnText={"Save"}
          />
          <span class="absolute bottom-9 right-8">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              class="w-10 h-10 text-purple-600"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
            >
              <path
                stroke-linecap="round"
                stroke-linejoin="round"
                stroke-width="2"
                d="M13 10V3L4 14h7v7l9-11h-7z"
              />
            </svg>
          </span>
        </div>
      </div>
    </Show>
  );
};

export default RequestModal;

```


![RestClientForm with simple html elements](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5xym87wxgj56ewq6bye.png)



Notice that we're passing a function against the `formSubmit` prop here. If a `request` is passed to this function in response to submitting the form, then we create a unique ID for that request using `self.crypto?.randomUUID()` (pretty neat trick TBH) and add the request to the requests list using the `setRestRequests` update function. Remember that this is part of a Signal, and not just a normal Signal but a Signal from `@solid-primitives/storage` package. So this will be updated both in the App's current state as well as in the local storage. Finally, you can see that we're calling the `props.onModalHide(id)` statement to close the modal after creating the new request and saving it.

But wait! If you try it out now, it won't work. As we're not using any controls, nor the `TextField` components yet. So let's create the controls first.

### Creating Solid Form Controls
We're now going to make use of the **Control Factory** from `solid-forms`. This allows us to create a **Form Group** that we can easily manage to handle form inputs and react to it.
To do so, update the `RestClientForm.tsx` file as follows:

```tsx
import { Component } from "solid-js";
import { IRestRequest } from "../interfaces/rest.interfaces";
import { createFormGroup, createFormControl } from 'solid-forms';

const controlFactory = () => {
  return createFormGroup({
    name: createFormControl<string>("New Request", {
      required: true,
      validators: (val: string) => {
        return !val.length ? {isMissing: true} : null;
      }
    }),
    request: createFormGroup({
      method: createFormControl<string>("GET"),
      body: createFormControl<string>(""),
      url: createFormControl<string>(""),
    }),
  });
};

// rest of the code

```

The `controlFactory` method returns a Form Group of type `IFormGroup` from Solid Forms. This means that we have a Form Group which can now have multiple Form Controls and Form Groups within it. In our case, we have the `name` as a Form Control as you can see it uses the `createFormControl` method. Notice that the first argument of the method is the initial value, and the second argument is the configuration which contains `required: true` and the `validators` method for custom validation.
The `request` property inside our Form Group is yet another Form Group which contains three Form Controls, `method`, `body`, and `url`.

Now we're going to wrap our `RestClientForm` component with solid forms using the `withControl` method. 
Create a wrapper component just below the `controlFactory` code as follows:

```tsx
import { Component } from "solid-js";
import { IRestRequest } from "../interfaces/rest.interfaces";
import { createFormGroup, createFormControl, withControl } from "solid-forms";
import { TextField } from "./TextField";

const controlFactory = () => {
  return createFormGroup({
    name: createFormControl<string>("New Request", {
      required: true,
      validators: (val: string) => {
        return !val.length ? { isMissing: true } : null;
      },
    }),
    request: createFormGroup({
      method: createFormControl<string>("GET"),
      body: createFormControl<string>(""),
      url: createFormControl<string>(""),
    }),
  });
};

export const RestClientForm = withControl<
  {
    request?: Partial<IRestRequest>;
    formSubmit: Function;
    formUpdate?: Function;
    actionBtnText: string;
  },
  typeof controlFactory
>({
  controlFactory,
  component: (props) => {
    const controlGroup = () => props.control.controls;
    const requestControlGroup = () => controlGroup().request.controls;
    const request = () => props.request;

    return (
      <form
        action=""
        class="space-y-4"
        classList={{
          "is-valid": props.control.isValid,
          "is-invalid": !props.control.isValid,
          "is-touched": props.control.isTouched,
          "is-untouched": !props.control.isTouched,
          "is-dirty": props.control.isDirty,
          "is-clean": !props.control.isDirty,
        }}
        onSubmit={(e) => {
          e.preventDefault();
          const params = {
            ...props.control.value,
            request: {
              ...props.control.value.request,
            },
          };
          props.formSubmit(params);
        }}
      >
        <div class="grid grid-cols-1 gap-4">
          <div>
            <label for="name" class="mb-4 block">
              Name
            </label>
            <TextField
              placeholder="name"
              id="name"
              label="Name"
              control={controlGroup().name}
            />
          </div>
          <div>
            <label for="url" class="mb-4 block">
              URL
            </label>
            <TextField
              placeholder="url"
              id="url"
              label="Url"
              control={requestControlGroup().url}
            />
          </div>

          <div>
            <label class="my-4 block">Method</label>
            <TextField
              id="method"
              label="Method"
              placeholder="method"
              control={requestControlGroup().method}
            />
          </div>
        </div>
        <div>
          <label class="my-4 block">Body</label>
          <TextField
            id="body"
            type="textarea"
            label="Body"
            placeholder="body"
            control={requestControlGroup().body}
          />
        </div>

        <div class="mt-4">
          <button
            disabled={!props.control.isValid}
            type="submit"
            class="inline-flex items-center disabled:bg-gray-500 justify-center w-full px-5 py-3 text-white bg-purple-600 hover:bg-purple-700 rounded-lg sm:w-auto"
          >
            <span class="font-medium"> {props.actionBtnText} </span>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              class="w-5 h-5 ml-3"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
            >
              <path
                stroke-linecap="round"
                stroke-linejoin="round"
                stroke-width="2"
                d="M14 5l7 7m0 0l-7 7m7-7H3"
              />
            </svg>
          </button>
        </div>
      </form>
    );
  },
});

```

In the above code, you'll notice that our `RestClientForm` component is now using the `withControl` method and uses the `controlFactory` we created before. As a result, we get the Solid Form (the Form Group) we create using the factory within the component function. The form group we're receiving is as `props.control` property. This is a Solid JS Signal. YES! Which means this is reactive. 
We distribute the controls into two groups.
- The main group using the `controlGroup` accessor
- The `request` control group using `requestControlGroup` accessor

You'll also notice that we get a bunch of properties from Solid Forms that we can use to apply different classes to our forms:

```jsx
<form
  action=""
  class="space-y-4"
  classList={{
    "is-valid": props.control.isValid,
    "is-invalid": !props.control.isValid,
    "is-touched": props.control.isTouched,
    "is-untouched": !props.control.isTouched,
    "is-dirty": props.control.isDirty,
    "is-clean": !props.control.isDirty,
  }}
</form>
```

And finally, we're passing each control to the respective `TextField` component via the `control` prop. Notice how we access the control by calling the accessor and then accessing the property. For example, `requestControlGroup().url` instead of `requestControlGroup.url`.

> Note: You'll notice that the element I've used for `method` is a `TextField` instead of a `<select>` component. This is intentional to not make the tutorial more complicated than it has to be. Feel free to adjust the code on your own 🙂

If you look at the app now, it should look as follows:

![RestClientForm with TextField components](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ergydr66xheqa0d6z5rx.png)

Looks cool 😎, right? Try creating a request now. You should be able to see it added to the UI as well as in the `localStorage`. This is because when we submit the form, we call the `props.formSubmit` method from the `RestClientForm`. This in turn calls the provided method in `RestclientForm` and that adds the new request.

## Solid JS Directives (your first one)
We're going to implement a Directive now. A directive is an implementation like a component, but it usually doesn't have a template and works works purely with the DOM most of the times. For our case, we're going to implement a directive that closes the `RequestModal` when we click outside of it. 
Create a new folder inside the `src` folder named `directives` and create a file inside it named `click-outside.directive.ts`. Then add the following code to it:

```ts
import { Accessor, onCleanup } from "solid-js";

export default function clickOutside(el: Element, accessor: Accessor<any>) {
  const onClick = (e: Event) =>
    !el.contains(e.target as Node) && accessor()?.();
  document.body.addEventListener("click", onClick);

  onCleanup(() => document.body.removeEventListener("click", onClick));
}

```

You can see the code is pretty minimal. But what does it do? 🤔
The directive itself is a function that has two arguments. SolidJS provides the first argument as the element the directive is applied to. And the second argument is the value provided to the directive by us. In this case, it will be a function that closes the modal.
Notice that we register a `click` event on `document.body` element which should check if we're clicking inside or outside the directive. And if we click outside, it calls the `accessor()` to get the function to be called, and then calls it via the `accessor()?.();` statement. Seems a bit confusing. Just get over it 😄

We also use the `onCleanup` hook from Solid JS to remove the event listener when the element to which the directive is applied to is no longer on the DOM. In our case, when we open the `RequestModal`, the directive would come to life and the event listener will be registered. And when the modal is closed, the directive will be destroyed and the event listener will be removed.

Let's use the directive now in the `RequestModal.tsx` as follows:

```tsx
import { Component, ComponentProps, Show } from "solid-js";
import { IRestRequest } from "../interfaces/rest.interfaces";
import { setRestRequests, restRequests } from "../store";
import { RestClientForm } from "./RestClientForm";
import outsideDirective from "../directives/click-outside.directive"; // <-- new import

// https://github.com/solidjs/solid/discussions/845
const clickOutside = outsideDirective; // <-- remapping to `clickOutside` variable

interface RequestModalProps extends ComponentProps<any> {
  show: boolean;
  onModalHide: (id: string | null) => void;
  request?: IRestRequest;
}

const RequestModal: Component<RequestModalProps> = (
  props: RequestModalProps
) => {
  return (
    <Show when={props.show}>
      <div class="fixed z-50 top-0 left-0 right-0 bottom-0 bg-[rgba(0,0,0,0.75)]">
        <div
          class="relative max-h-[85%] overflow-y-auto top-20 bg-gray-200 max-w-md m-auto h- block p-8 pb-8 border-t-4 border-purple-600 rounded-sm shadow-xl"
          use:clickOutside={() => {  {/** Using the directive here */}
            props.onModalHide(null);
          }}
        >
          <h5 class="text-4xl font-bold mb-4">
            {(props.request ? "Edit" : "Create") + " Request"}
          </h5>
          <RestClientForm
            ...
          />
          <span class="absolute bottom-9 right-8">
            ...
          </span>
        </div>
      </div>
    </Show>
  );
};

export default RequestModal;

```

You'll notice that TypeScript isn't happy again as it doesn't understand what `clickOutside` is as a directive. 


![directive making TS sad](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6fmrss5yc4qlxts3ijes.png)

Let's make TypeScript happy. Update the `solid-js.d.ts` file inside the `types` folder as follows:

```ts
import "solid-js";

declare module "solid-js" {
  namespace JSX {
    interface IntrinsicElements {
      "ion-icon": any;
    }
    interface Directives {
      clickOutside?: () => void;
    }
  }
}
```

TypeScript should be happy now! You may have also noticed that we're doing a remapping of variables there from `outsideDirective` to `clickOutside`. That's because of a TypeScript + Solid JS issue that I've linked in the code. 
If you open the Request Modal now and click outside of it, you'll see the modal closing automatically. Yayy! 🙌

## Deleting Requests
Now that we can add requests, close the modal automatically on clicking outside and save the requests persistently, let's work on deleting requests.
To delete a request we'll add a delete button on each list item in the left sidebar. To do that, update the `Home.tsx` as follows:

```tsx
// existing imports
import { restRequests, setRestRequests } from "../store";

const Home: Component = () => {
  const [showModal, setShowModal] = createSignal(false);
  const navigate = useNavigate();
  const location = useLocation();
  return (
    <div class="flex flex-col md:flex-row gap-4 h-full flex-1">
      <div>
        <RequestModal
          show={showModal()}
          onModalHide={(id: string | null) => {
            setShowModal(!showModal());
          }}
        />
      </div>
      <div class="w-full md:w-1/4 bg-gray-200 min-h-full border-gray-300 border p-4 rounded-lg">
        <div class="flex justify-between py-4">
          <h1 class="text-sm ">Rest Requests</h1>
          {/* Replaced the Add Request Button with IconButton */}
          <IconButton
            onClick={() => setShowModal(true)}
            icon="add"
            label="Add Request"
          />
        </div>

        <div class="list">
          <For each={restRequests()} fallback={<div>Loading...</div>}>
            {(item) => (
              <Link href={`/${item.id}`} class="relative list__item">
                <div
                  class="p-2 hover:bg-gray-300 cursor-pointer pr-12 rounded-lg mb-2"
                  classList={{
                    "list__item--active": Boolean(
                      location.pathname === `/${item.id}`
                    ),
                  }}
                >
                  <div>{item.name}</div>
                  <div class="text-xs break-all">
                    {item.request.method} {item.request.url}
                  </div>
                </div>
                {/* Delete Request Button */}
                <button
                  onclick={(e: MouseEvent) => {
                    e.preventDefault();
                    e.stopImmediatePropagation();
                    if (restRequests()?.length) {
                      const requests = restRequests() || [];
                      setRestRequests(requests.filter((i) => i.id !== item.id));
                      if (location.pathname === `/${item.id}`) {
                        navigate("/");
                      }
                    }
                  }}
                  class="absolute text-xl hover:scale-125 transition-all ease-in-out duration-100 hover:text-red-700 text-red-600 right-2 top-0 bottom-0 m-auto"
                >
                  <ion-icon name="trash"></ion-icon>
                </button>
              </Link>
            )}
          </For>
        </div>
      </div>
      <div class="flex-1 min-h-full">
        <Outlet />
      </div>
    </div>
  );
};

export default Home;

```

If you look at the app after this change, you're going to see a delete button on every request. We're going to change that to show it only on the item being hovered. 
Look at the `onclick` handler of the delete button, notice that we use the `requests.filter` method to remove the request from array using the request's `id`. And we're then using the `setRestRequests` updater function to persist them. Try the delete button to delete some requests.

Let's add some CSS to make the delete button visible only on hover of the request item. Create a new file named `Home.css` and add the following code inside it:

```css
.list .list__item ion-icon {
  display: none;
}

.list .list__item:hover ion-icon {
  display: flex;
}

.list .list__item:hover .list__item--active + ion-icon {
  @apply text-white;
}

.list .list__item--active {
  @apply bg-purple-600 text-white;
}

```

Finally, import the `Home.css` file inside `Home.tsx` file as follows:

```tsx
// existing imports
import { restRequests, setRestRequests } from "../store";
import "./Home.css";

```

If you look at the app now, we only show the delete button on the hovered request item. 


![Delete btn only on hover](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uen69jaw1nzgf0mseuxa.png)

Awesome, we're now able to create requests, delete requests, save then persistently and have a really good UI/UX for the app. Feel free to modify how it looks and make it your own 🎉

## Conclusion
In this tutorial, we've learnt a couple of concepts when it comes to Solid JS. We learnt:
- Getting started with Solid JS
- Signals
- Persisting data into storage
- Working with Routes using `solid-app-router`
- Working with Forms using `solid-forms`
- Creating directives

I enjoyed writing this article and I hope you learnt quite a few things from it. If you did, do react 👏 to this post and bookmark it. This tutorial is also available on YouTube so you can check out both [Part1](https://youtu.be/L1f30LR5C_o) and [Part2](https://youtu.be/5jI1rc9dwgw) there.

If you'd like to connect, here are the links to my socials:

[Twitter](https://twitter.com/codewith_ahsan), [YouTube](https://youtube.com/codewithahsan), [LinkedIn](https://linkedin.com/ahsanayaz), [GitHub](https://github.com/ahsanayaz)