Building a VS Code Extension to Fetch and Display Content from an API
Tags: react, typescript, api, vscode
Published: 2024-07-12 05:52:12

This post deals with building a VS Code extension using React, Typescript and Tailwind CSS.

So, you've decided to build a VS Code extension. Great choice! You want to fetch and display content from an API, because, let's face it, hardcoding stuff is so 90s. Buckle up, because we're about to embark on a journey that will involve code, APIs, and probably a few cups of coffee. Let's keep it fun and functional, shall we?

---

## PAGE CONTENT

- [Prerequisites] (#p-1)
- [Getting Started] (#p-2)
- [Setting Up Your Workspace] (#p-3)
- [Fetching Data from an API] (#p-4)
- [Displaying API Content] (#p-5)
- [Conclusion] (#p-6)

---

## PREREQUISITES <a name="p-1"></a>

Before we begin, make sure you have the following installed:

- [Node.js](https://nodejs.org/en) (for running JavaScript/TypeScript)
- [Visual Studio Code](https://code.visualstudio.com/) (your coding sanctuary)

---

## STEP 1: GETTING STARTED <a name="p-2"></a>

First things first, fire up VS Code and create a new project. If you're like me, you spend a good 10 minutes deciding on the project name - because naming things is the hardest part of programming, right?

### Creating Your Project Directory

Create a folder and open it with VS Code.

```
mkdir my-vscode-extension
cd my-vscode-extension
```

You'll see the below:

![vscode folder creation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nf7tm4k8pr3jdp4fcpvc.png)



### Creating an Extension

Now open the VS Code terminal. 

#### Install `Yeoman` and VS Code Extension Generator:
If you haven't already installed Yeoman and the VS Code Extension Generator, do so globally using `npm`:

```
npm install -g yo generator-code
```

#### Generate Your Extension:
Run the Yeoman generator to scaffold out your VS Code extension project:

```
yo code
```

![Yo code VS Code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dghysvdnf8tkzrc4xr8e.png)

Select **Typescript** and proceed.

#### Follow the prompts in the terminal:

![VS Code extension](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bvhhm5z9cd7d38jkg0hq.png)

This will create a basic structure for your VS Code extension project.
Now select **'Open with code'** and jump to the next step.

#### Explore Your Extension Structure:
You should see something like the below:

![VS Code extension structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ecgn1cyge6j4fae7dnsn.png)

The generator sets up a basic folder structure and files including:

- `src/extension.ts`: Entry point for your extension
- `package.json`: Contains extension metadata and scripts
- `tsconfig.json`: TypeScript configuration file
- `.vscode/`: VS Code-specific configuration

For more details on what each file is about, go to [anatomy](https://code.visualstudio.com/api/get-started/extension-anatomy).

### Creating Your React App

For this and next step, I would highly recommend to go to [BestAmazingPost](https://dev.to/rakshit47/create-vs-code-extension-with-react-typescript-tailwind-1ba6)
although I have given an overview.

Create a React app with typescript in the **root** directory (in my case **Quotebee**).
Give any name you want. In this case, it is **bee**

```
npx create-react-app bee --template typescript
cd bee
```

So now your final project structure should look something like this:


![VS Code extension with react](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zkvto8ye37s70uvdynk.png)


And yay! You are set to configure your workspace.

---

## STEP 2: SET UP YOUR WORKSPACE <a name="p-3"></a>

Now, let's configure our workspace. In this section, we will make necessary changes to the following files and install dependencies to make a basic working extension.

### Set up Tailwind:
Inside react app **bee**, set up tailwindcss

```
npm i -D tailwindcss postcss
npx tailwindcss init
```
This will generate a file `tailwind.config.js` file in your project directory.

### Edit `tailwind.config.js`

```
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

### Edit `bee/src/index.css`

```
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  @apply p-0;
}
```

### Create `postcss.config.js` file:

```
module.exports = {
    plugins: {
      tailwindcss: {},
      autoprefixer: {},
    },
};
```

### Edit `App.tsx` in `bee/src/App.tsx`

```
function App() {
  return (
    <div className="bg-gradient-to-r from-pink-700 to-blue-700 p-10">
      <p className="text-white/80 text-xl font-semibold">
        DEV
      </p>
    </div>
  );
}

export default App;

```

### Set up Parcel

You may use any bundler though!

```
npm i -D parcel
```

### Edit `package.json` of react app directory

```
//bee/package.json
{
"source": "src/index.tsx",
"scripts": {
    "start": "parcel", 
    "build": "parcel build", 
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
}
```

### Run Parcel

```
npm start
```
### Run `npm run compile` in root directory. 

This should create an `out ` folder.

```
npm run compile
```

### Edit `extension.ts`

```
import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {

  let webview = vscode.commands.registerCommand('quotebee.helloWorld', () => {

    let panel = vscode.window.createWebviewPanel("webview", "Quotebee", vscode.ViewColumn.Two, {
        enableScripts: true
    });

    // Paths to resources
    let scriptSrc = panel.webview.asWebviewUri(vscode.Uri.joinPath(context.extensionUri, "bee", "dist", "index.js"))
    let cssSrc = panel.webview.asWebviewUri(vscode.Uri.joinPath(context.extensionUri, "bee", "dist", "index.css"))

    const apiUrl = 'https://quotes-api-self.vercel.app/quote';

    panel.webview.html = `<!DOCTYPE html>
    <html lang="en">
      <head>
        <link rel="stylesheet" href="${cssSrc}" />
      </head>
      <body>
        <noscript>You need to enable JavaScript to run this app.</noscript>
        <div id="root" data-api-url="${apiUrl}"</div>
        <script src="${scriptSrc}"></script>
      </body>
    </html>
    `;
});

  context.subscriptions.push(webview);
}

export function deactivate() { }

```
**NOTE:** Make sure to edit your trigger command. In my case it is `quotebee.helloWorld`. It should be consistent in package.json too!

**NOTE AGAIN:** Make sure VScode version is same in `Help->About`, package.json and package-lock.json.

Woohoo! Now grab a bucket (of enthusiasm) and go fetching! (from API)

---
## FETCHING DATA FROM API <a name="p-4"></a>

In modern web development, dynamic content is often fetched from APIs. APIs, or Application Programming Interfaces, provide a way for different software applications to communicate with each other. 

We'll use the [https://quotes-api-self.vercel.app/quote API](https://quotes-api-self.vercel.app/quote API), which generates random quotes. This simple API returns a JSON response with a quote and its author. Let's break down how to fetch and display this data in a React component.

### API Working Diagram

Before we charge ahead, let’s map out our journey with a simple diagram to understand how the API call works:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0566zfh8e9rrui7ij3jo.png)


1. The React component sends a request to the wise Quotes API.
2. The Quotes API responds with a JSON scroll containing a random quote and its author.
3. The React component processes this data and updates the UI to display the quote and author.

There are many functions used to fetch content from API and you can read them from [here](https://www.freecodecamp.org/news/how-to-fetch-api-data-in-react/).

### CODE OVERVIEW

- Setup React Component: We’ll use React's functional component and hooks (useState and useEffect) to manage the state and lifecycle of the component.
- State Management: We’ll define state variables to store the fetched quote and author.
- Fetch Data: We’ll use the fetch function inside a useEffect hook to make an API request when the component mounts.
- Display Data: We’ll display the fetched quote and author in the component's JSX.

### BUILDING THE CODE

Imports: We import useState and useEffect from React to manage state and side effects.

```
import React, { useState, useEffect } from 'react';

```
Component Definition: We define a functional component App.

```
function App() {

```
State Variables: We declare state variables quote and author using the useState hook.

```
const [quote, setQuote] = useState('');
const [author, setAuthor] = useState('');

```
Fetching Data: Inside the useEffect hook, we define and call the fetchQuote function to fetch data from the API. The fetch function sends a GET request to the API, and we process the JSON response to update the state.

```
useEffect(() => {
  const fetchQuote = async () => {
    try {
      const response = await fetch('https://quotes-api-self.vercel.app/quote');
      const data = await response.json();
      setQuote(data.quote); // Set the quote
      setAuthor(data.author); // Set the author
    } catch (error) {
      console.error('Error fetching quote:', error);
    }
  };

  fetchQuote();
}, []); // Empty dependency array means this effect runs once after the initial render

```
Rendering Data: We use JSX to display the quote and author. If the quote is not yet fetched, we show a loading message. The author's name is displayed conditionally only if it's available.

```
return (
  <div className="bg-gradient-to-r from-pink-700 to-blue-700 p-10">
    <p className="text-white/80 text-xl font-semibold">
      {quote ? `"${quote}"` : 'Loading...'}
    </p>
    {author && (
      <p className="text-white/60 text-lg font-medium">
        - {author}
      </p>
    )}
  </div>
);

```
Export Component: Finally, we export the App component.

```
export default App;

```

### THE CODE 

```
import React, { useState, useEffect } from 'react';

function App() {
  const [quote, setQuote] = useState('');
  const [author, setAuthor] = useState('');

  useEffect(() => {
    // Function to fetch data from the API
    const fetchQuote = async () => {
      try {
        const response = await fetch('https://quotes-api-self.vercel.app/quote');
        const data = await response.json();
        setQuote(data.quote); // Set the quote
        setAuthor(data.author); // Set the author
      } catch (error) {
        console.error('Error fetching quote:', error);
      }
    };

    fetchQuote();
  }, []); // Empty dependency array means this effect runs once after the initial render

  return (
    <div className="bg-gradient-to-r from-pink-700 to-blue-700 p-10">
      <p className="text-white/80 text-xl font-semibold">
        {quote ? `"${quote}"` : 'Loading...'}
      </p>
      {author && (
        <p className="text-white/60 text-lg font-medium">
          - {author}
        </p>
      )}
    </div>
  );
}

export default App;

```

---

## DISPLAYING THE CONTENT <a name="p-5"></a>

The output of what we have made so far is this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zyi8yrfv91h3xlpalm5z.png)

Each time you open the extension, it will display a random quote.

Is this the best UI we can make? Absolutely not! That depends on how much of a UI wizard you are! Explore the various panes of VS Code from [here](https://code.visualstudio.com/api/ux-guidelines/overview) and decide on what's good for your purpose!

---

## CONCLUSION <a name="p-6"></a>

You've made it to the end, and that's no small feat! It's been amazing to have you here, and I hope you've enjoyed this coding journey as much as I have.

In this blog, we learned how to fetch data from an API and display it in a React application. We used the fetch function to retrieve data from the Quotes API and employed React hooks to manage the state and lifecycle of our component. This approach can be extended to other APIs and data sources, making it a powerful tool for building dynamic, data-driven applications.

By mastering the art of fetching and displaying data in React, you can create more interactive and engaging user experiences. Now, go forth and may your code be ever bug-free and your APIs responsive! Happy coding!😀