How to setup Vite React App!

karan siddhukaran siddhu
5 min read

Introduction ๐Ÿ“ข

Hey geeky devs, today we are going to build a an awesome Vite React App Project Setup that will grow as our Client unnecessary expectations. ๐Ÿฅด

Prerequisites ๐ŸŽฏ

  • Little bit knowledge in JavaScript/TypeScript

  • Little bit knowledge in React

  • A whole lot of motivation (naa just kidding, this one is easy)

Setting Up ๐Ÿ› ๏ธ

  • Node > 20 LTS

  • Your Favourite IDE

  • npm or yarn or pnpm or whatever floats your boat โ›ด๏ธ

Why React? ๐Ÿค”

React is still, and always will be, one of the best ways for a beginner to start Frontend development and build a career in frontend. According to the Stack Overflow Survey 2024, React is still the top choice for professional developers and people who are learning to code! That says a lot.

Step 1: Build A Vite React App

Use the following command to build a React Vite App and choose the options as mentioned below.

yarn create vite

Step 2: Install the Required Library

1st: Install the library that makes styling easier and reduces the use of CSS

This section depends on your preferences and which UI library you prefer. However, my suggestion is to choose the one that will simplify development as your project grows. I would go with Material UI for this tutorial.

yarn add @mui/material @emotion/react @emotion/styled @mui/icons-material -D

2nd: Install and Setup Eslint and Prettier

It's convenient that Vite automatically configures ESLint when setting up an app. Next, we just need to configure Prettier.

yarn add prettier eslint-config-prettier eslint-plugin-prettier -D

3rd: Install State Management Library

This section depends on your preferences, as React offers many options to choose from. The most popular state management libraries include Redux, Zustand, and Recoil, among others.

Choose the one that best aligns with your project requirements. For simplicity of this tutorial I will go with plain Context API

Step 3: Folder Structure

Here is the breakdown for the folder structure each module wise

vite-react-app/
โ”œโ”€โ”€ public/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ assets/
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”œโ”€โ”€ hooks/
โ”‚   โ”œโ”€โ”€ pages/
โ”‚   โ”œโ”€โ”€ config/
|   โ”œโ”€โ”€ contexts/
โ”‚   โ”œโ”€โ”€ types/
โ”‚   โ”œโ”€โ”€ utils/
โ”‚   โ”œโ”€โ”€ App.tsx
โ”‚   โ”œโ”€โ”€ main.tsx
|   โ”œโ”€โ”€ index.css
โ”‚   โ”œโ”€โ”€ vite-env.d.ts

Assets

src/
โ”œโ”€โ”€ assets/
โ”‚   โ”œโ”€โ”€ icons/
โ”‚   โ”‚   โ”œโ”€โ”€ message-icon.svg
โ”‚   โ”‚   โ””โ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ images/
โ”‚   โ”‚   โ”œโ”€โ”€ hero.svg
โ”‚   โ”‚   โ””โ”€โ”€ index.ts

Components

src/
โ”œโ”€โ”€ components/
โ”‚   โ”œโ”€โ”€ button/
โ”‚   โ”‚   โ”œโ”€โ”€ CustomButton.tsx
โ”‚   โ”‚   โ””โ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ header/
โ”‚   โ”‚   โ”œโ”€โ”€ CustomHeader.tsx
โ”‚   โ”‚   โ””โ”€โ”€ index.ts

Context

src/
โ”œโ”€โ”€ context/
โ”‚   โ”œโ”€โ”€ HttpMethodContext.ts

HttpMethodContext.ts

import axios from 'axios';
import React, { createContext, useCallback, useContext, useState } from 'react';
import { ApiResponseData } from '../types/api';
import envConfig from '../config/env.config';

interface HttpMethodContextType {
  showApiLoader: boolean;
  setShowApiLoader: React.Dispatch<React.SetStateAction<boolean>>;
  get: (endpoint: string, showLoader?: boolean) => Promise<ApiResponseData>;
  post: (
    endpoint: string,
    data: object | Array<object>,
    showLoader?: boolean,
    header?: object
  ) => Promise<ApiResponseData>;
  put: (
    endpoint: string,
    data: object | Array<object>,
    showLoader?: boolean
  ) => Promise<ApiResponseData>;
  deleteMe: (
    endpoint: string,
    body: Array<object> | object,
    showLoader?: boolean
  ) => Promise<ApiResponseData>;
}

export const HttpMethodContext = createContext<
  HttpMethodContextType | undefined
>(undefined);

const AxiosService = axios.create({
  baseURL: envConfig.API_URL
});

const createApiErrorResponse = (error: unknown): ApiResponseData => {
  let errorMsg = 'Something went wrong';

  if (error instanceof String) {
    errorMsg = error.toString();
  } else if (error instanceof Error) {
    errorMsg = error.message;
  }

  return { success: false, errorMsg, response: {} };
};

export const HttpMethodContextProvider: React.FC<{
  children: React.ReactNode;
}> = ({ children }) => {
  const [showApiLoader, setShowApiLoader] = useState(false);

  AxiosService.defaults.headers.common.Accept = 'application/json';
  AxiosService.defaults.headers.common['Content-Type'] = 'application/json';

  const get = useCallback(
    async (endpoint: string, showLoader = true): Promise<ApiResponseData> => {
      setShowApiLoader(showLoader);

      return AxiosService.get(endpoint)
        .then((res) => {
          console.log(`GET: ${endpoint}:`, res.status);
          return {
            success: true,
            errorMsg: '',
            response: res.data
          };
        })
        .catch((err) => {
          console.log(`๐Ÿ›‘ GET: ${endpoint}:`, err?.response?.data ?? err);
          return createApiErrorResponse(err);
        })
        .finally(() => setShowApiLoader(false));
    },
    [setShowApiLoader]
  );

  const post = useCallback(
    async (
      endpoint: string,
      data: object | Array<object>,
      showLoader = true,
      headers = {}
    ): Promise<ApiResponseData> => {
      setShowApiLoader(showLoader);

      return AxiosService.post(endpoint, data, headers)
        .then((res) => {
          console.log(`POST: ${endpoint} res`, res.status);
          return {
            success: true,
            errorMsg: '',
            response: res.data
          };
        })
        .catch((err) => {
          console.log(`๐Ÿ›‘ POST - ${endpoint} err`, err?.response?.data ?? err);
          return createApiErrorResponse(err);
        })
        .finally(() => setShowApiLoader(false));
    },
    [setShowApiLoader]
  );

  const put = useCallback(
    async (
      endpoint: string,
      data: object | Array<object>,
      showLoader = true
    ): Promise<ApiResponseData> => {
      setShowApiLoader(showLoader);

      return AxiosService.put(endpoint, data)
        .then((res) => {
          console.log(`PUT: ${endpoint} res`, res.status);
          return {
            success: true,
            errorMsg: '',
            response: res.data
          };
        })
        .catch((err) => {
          console.log(`๐Ÿ›‘ PUT - ${endpoint} err`, err?.response?.data ?? err);
          return createApiErrorResponse(err);
        })
        .finally(() => setShowApiLoader(false));
    },
    [setShowApiLoader]
  );

  const deleteMe = useCallback(
    async (
      endpoint: string,
      body: Array<object> | object,
      showLoader = true
    ): Promise<ApiResponseData> => {
      setShowApiLoader(showLoader);

      return AxiosService.delete(endpoint, { data: body })
        .then((res) => {
          console.log(`DELETE: ${endpoint} res`, res.status);
          return {
            success: true,
            errorMsg: '',
            response: res.data
          };
        })
        .catch((err) => {
          console.log(
            `๐Ÿ›‘ DELETE - ${endpoint} err`,
            err?.response?.data ?? err
          );
          return createApiErrorResponse(err);
        })
        .finally(() => setShowApiLoader(false));
    },
    [setShowApiLoader]
  );

  return (
    <HttpMethodContext.Provider
      value={{ showApiLoader, setShowApiLoader, get, post, put, deleteMe }}
    >
      {children}
    </HttpMethodContext.Provider>
  );
};

export const useHttpMethodContext = () => {
  const context = useContext(HttpMethodContext);

  if (!context) {
    throw new Error('useHttpMethodContext must be used within a UserProvider');
  }

  return context;
};

Hooks

src/
โ”œโ”€โ”€ hooks/
โ”‚   โ”œโ”€โ”€ api/
โ”‚   โ”‚   โ”œโ”€โ”€ useUserApi.ts

useUserApi.ts

import { useHttpMethodContext } from '../../context/HttpMethodProvider';
import { ApiResponseData } from '../../types/api';

const useUserApi = () => {
  const { get } = useHttpMethodContext();

  const getAllUser = async (showApiLoader = true): Promise<ApiResponseData> => {
    const response = await get('/user', showApiLoader);

    // Add a parser if required

    return response;
  };

  // other APIs related to users  

  return { getAllUser };
};

export default useUserApi;

Pages

src/
โ”œโ”€โ”€ pages/
โ”‚   โ”œโ”€โ”€ home/
โ”‚   โ”‚   โ”œโ”€โ”€ Home.tsx
โ”‚   โ”œโ”€โ”€ user/
โ”‚   โ”‚   โ”œโ”€โ”€ UserDetails.tsx

Config

src/
โ”œโ”€โ”€ config/
โ”‚   โ”œโ”€โ”€ env.config.ts

Example of env.config.ts

export default {
    API_URL: import.meta.env.VITE_API_URL,
    APP_ENVIRONMENT: import.meta.env.VITE_APP_ENVIRONMENT || 'dev'
};

Step 4: Conclusion

With this, I conclude this tutorial. Let me know what you guys think about it and share your thoughts in the comment section.

0
Subscribe to my newsletter

Read articles from karan siddhu directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

karan siddhu
karan siddhu