haiBlog Notes

website·Indexed 30 days ago

https://ocean0hai.github.io/haiBlog

A collection of technical notes and experience sharing covering React, JavaScript fetch implementation, and general technology learning.

Tokens
2.4K
Snippets
8
Records
8
Agent score
75%

What's inside haiBlog

  1. Download files as Blobs using the Fetch API

    To download a file using the Fetch API, request the data as a Blob. You can then create a temporary object URL using window.URL.createObjectURL(blob), attach it to a hidden anchor (<a>) element, and programmatically trigger a click to start the download. It is important to call window.URL.revokeObjectURL(url) after the download to free up memory.
    async function blobResponseHandle(response: Response, autoDownLoad: boolean, fileName: string) {
      if (autoDownLoad) {
        const blob = await response.clone().blob();
        const contentDispositionHeader = response.headers.get('Content-Disposition') || '';
        const match = contentDispositionHeader.match(/filename=["']?([^"']+)["']?/);
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = decodeURIComponent(match ? match[1] : fileName || 'defaultName_file');
        a.click();
        window.URL.revokeObjectURL(url);
      }
    }
  2. Implement an infinite autoplay carousel in React

    To create a seamless infinite scrolling effect, the list of items must be at least twice the width (or height) of the container. If the original data length is less than the number of items displayed on one screen (showNum), the data should be padded by repeating the original set until the total count is greater than or equal to showNum before duplicating the entire list for the loop. The animation is achieved by translating the list by half of its total height/width and then resetting.
    import { useEffect } from 'react';
    import * as React from 'react';
    
    interface Props {
      showNum: number;
      speed: number;
      containerWidth?: number;
      containerHeight: number;
      data: any[];
      hoverStop?: boolean;
      direction?: 'left' | 'right';
      children?: React.ReactNode;
    }
    
    function AutoplayCarousel({
      showNum,
      speed,
      containerHeight,
      data,
      hoverStop = false,
      direction = 'left',
      children,
    }: Props) {
      const container = React.useRef<HTMLDivElement>(null);
      const length = data.length * 2;
      const itemHeight = containerHeight / showNum;
    
      useEffect(() => {
        const style = document.createElement('style');
        let start = '0';
        let end = `-${(itemHeight * length) / 2}`;
        if (direction === 'right') {
          start = end;
          end = '0';
        }
    
        style.innerText = `
          @keyframes templates-partner-moving {
            0% { transform: translateY(${start}px); }
            100% { transform: translateY(${end}px); }
          }
        `;
        if (hoverStop) {
          style.innerText += `.list:hover { animation-play-state: paused !important; }`;
        }
        document.head.appendChild(style);
        return () => {
          if (document.head.contains(style)) document.head.removeChild(style);
        };
      }, [direction, length, itemHeight, hoverStop]);
    
      return (
        <div ref={container} style={{ width: 400, height: containerHeight }} className="wrap">
          <div
            className="list"
            style={{
              animation: `templates-partner-moving ${(length / showNum / 2) * speed}s infinite linear`,
            }}
          >
            {children}
            {children}
          </div>
        </div>
      );
    }
    
    export default AutoplayCarousel;
  3. Download files using Axios with responseType blob

    When downloading files with Axios, you must set responseType: 'blob'. This ensures the response data is handled as a Blob rather than a string or JSON. Once the blob is received, create a local URL and trigger a browser download using a temporary anchor element.
    async function down(name: string) {
      await axios({
        method: 'get',
        url: `http://api.example.com/download/${name}`,
        responseType: 'blob',
        headers: { 'Authorization': localStorage.getItem('token') }
      }).then(res => {
        const blob = new Blob([res.data]);
        const down = document.createElement('a');
        down.download = name;
        down.style.display = 'none';
        down.href = URL.createObjectURL(blob);
        document.body.appendChild(down);
        down.click();
        URL.revokeObjectURL(down.href);
        document.body.removeChild(down);
      });
    }
  4. Upload files using Fetch and FormData

    To upload a file via the Fetch API, use the FormData object to encapsulate the file from an HTML input element. Set the request method to POST and pass the formData instance as the body. The browser will automatically set the correct Content-Type header (multipart/form-data).
    const updateImg = document.querySelector<HTMLInputElement>("#updateImg")!;
    updateImg.addEventListener('change', (e: any) => {
        const formData = new FormData();
        formData.append('File', e.target.files[0]);
        
        fetch('https://api.com/UploadImgFile', {
            method: 'POST',
            body: formData
        })
        .then(response => response.json())
        .then(result => console.log('Success:', result))
        .catch(error => console.error('Error:', error));
    });
  5. Use the Fetch wrapper to call APIs

    When using the wrapped get, post, put, or delet functions, pass a generic type T to define the expected response structure. Because the wrapper uses Promise.reject for both HTTP errors and business code errors, calls should be wrapped in a try...catch block.
    // Define an API call using the generic get wrapper
    export async function getAllLives(id?: string) {
      const res = id ? `?id=${id}` : "";
      // T is the expected response type
      return get<T>(FetchAPIPath.GetAllLives + res);
    }
    
    // Execute the call with error handling
    async function getData() {
        try {
          const res = await getAllLives();
          if (res.code === 200) {
            setData(res.result);
          }
        } catch (error) {
          console.warn("Request failed:", error);
        }
    }
  6. Implement a Fetch API wrapper with response handling

    This implementation wraps the native fetch API to provide a standardized way of handling HTTP requests. It includes a multi-stage response pipeline: first checking the HTTP status code (allowing 200, 304, 201), then parsing the JSON body, and finally validating a business-level response code (allowing 0 or 200). It also automatically attaches a Bearer token from local storage to the request headers.
    import { notification } from "antd";
    import { BaseApiResponse } from "@/utils/fetchExt";
    import queryString from "query-string";
    
    // Helper to retrieve auth token from localStorage
    export function getAuthorization() {
      const AccessTokenAdmin = localStorage.getItem("access_token");
      if (AccessTokenAdmin) {
        return { Authorization: `Bearer ${AccessTokenAdmin}` };
      }
      return undefined;
    }
    
    // Stage 1: Validate HTTP Status
    export async function resposneStateHandler(response: Response) {
      const { status, statusText } = response;
      const releaseArr = [200, 304, 201];
      if (releaseArr.includes(status)) {
        return Promise.resolve(response);
      } else {
        console.warn("Error occurred", statusText);
        return Promise.reject(response);
      }
    }
    
    // Stage 2: Validate Business Code in JSON body
    function responseCodeHandler<T>(response: BaseApiResponse<T>): Promise<BaseApiResponse<T>> {
      const code = response.code || 0;
      const message = response.message;
      const codeArr = [0, 200];
      if (codeArr.includes(Number(code))) {
        return Promise.resolve(response);
      }
      notification.error({
        message,
        description: `[${code}]:${message}`,
      });
      return Promise.reject(response);
    }
    
    // Pipeline orchestrator
    function responseHandle<T>(response: Response) {
      return Promise.resolve(response)
        .then((res) => resposneStateHandler(res))
        .then((res) => res?.json())
        .then((res) => responseCodeHandler<T>(res));
    }
    
    // GET request with query string support
    export async function get<T>(url: string, data?: { [key: string]: unknown }): Promise<BaseApiResponse<T>> {
      const locationSearch = data && queryString.stringify(data);
      const finUrl = locationSearch ? `${url}?${locationSearch}` : url;
      return fetch(finUrl, {
        headers: {
          "Content-Type": "application/json",
          ...getAuthorization(),
        },
      }).then((response) => responseHandle<T>(response));
    }
    
    // POST request
    export function post<T>(Url: string, data?: any): Promise<BaseApiResponse<T>> {
      return fetch(Url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...getAuthorization(),
        },
        body: typeof data === "string" ? data : JSON.stringify(data),
      }).then((response) => responseHandle<T>(response));
    }
    
    // PUT request
    export async function put<T>(url: string, body: any): Promise<BaseApiResponse<T>> {
      const response = await fetch(url, {
        method: "PUT",
        headers: { "Content-Type": "application/json", ...getAuthorization() },
        body: JSON.stringify(body),
      });
      if (!response.ok) throw new Error(response.statusText);
      return await response.json();
    }
    
    // DELETE request
    export async function delet<T>(url: string, data?: any): Promise<BaseApiResponse<T>> {
      const response = await fetch(url, {
        method: "DELETE",
        headers: { "Content-Type": "application/json", ...getAuthorization() },
        body: JSON.stringify(data),
      });
      if (!response.ok) throw new Error(response.statusText);
      return await response.json();
    }
  7. Use the AutoplayCarousel component

    Pass the required dimensions, the number of items to show per screen, and the speed. The items to be scrolled should be passed as children, typically by mapping over a data array.
    <AutoplayCarousel
      containerHeight={123.09}
      showNum={3}
      speed={5}
      data={data}
    >
      {data.map((item, index) => (
        <div className="scrollItem" key={index}>
          <div className="type">
            <span>{item.type}</span>
          </div>
          <div className="title">
            {item.title}
          </div>
        </div>
      ))}
    </AutoplayCarousel>