Efficient use of requestAnimationFrame()

I have hundreds of list items to be animated at the same time on list updates. The easiest way to implement it, would be to use a function I have already ready:

export function moveElement(el: HTMLElement, options: {
    toLeft?: string | undefined;
    toTop?: string | undefined;
    durationMs?: number | undefined;
    fromLeft?: string | undefined;
    fromTop?: string | undefined;
    signal?: AbortSignal | undefined;
    runInAnimationFrame?: boolean | undefined;
}): Promise<void> {
    if (runInAnimationFrame) {
        return runInAnimationFrame(() => {
            moveElementIntern(el, options);
        })
    } else {
        return moveElementIntern(el, options);
    }
}

moveElementIntern() uses runInAnimationFrame eventually several times depending on the options:

export function runInAnimationFrame(cb: (ms: number) => void, signal?: AbortSignal): Promise<void> {

    return new Promise<void>((res, rej) => {
        function wrappedCb(ms: number) {
            try {
                cb(ms);
                res();
            } catch (reason) {
                rej(reason);
            } finally {
                signal?.removeEventListener('abort', onAbort);
            }
        }
        const id = requestAnimationFrame(wrappedCb);

        function onAbort() {
            assert(signal?.aborted);
            cancelAnimationFrame(id);
            signal.removeEventListener('abort', onAbort);
            rej(signal.reason);
        }

        signal?.addEventListener('abort', onAbort);

    })

}

Now, as I imagine for example 500 calls to moveElement parallelly, I think about if it would be more efficient to replace the hundreds of calls to moveElement which produce each for example 2 calls to requestAnimationFrame by a only one call to a new function

function moveElements(tasks: {el: HTMLElement, options: { ... }}[]) { ... }

This would mean that requestAnimationFrame is only called about 2 times instead of about 1000 times. But there would be much more logic needed in moveElements to group the actions for all elements.

So, the decision depends mainly on the effeciency of the implementation of requestAnimationFrame in the main browsers. To be honest, I only care about Firefox, Chrome, Safari, and its mobile variants.

Has anybody enough experience to answer the question without needing to implement the new version and testing it on the different browsers? Would be very appreciated! 🙂

Javascript nested list toggle problem when searching

the main issue is, when i search a list that is a parent and if i clicked that parent list it will not toggle the child of that list…

it should toggle the child when i clicked the matched parent list of the search result.

the search is real time it will automatically update the page.

document.addEventListener('DOMContentLoaded', function() {
  const searchBox = document.getElementById('menuSearch');

  // Toggle dropdowns manually
  document.querySelectorAll('.shopMenuList li.has-children > span').forEach(toggle => {
    toggle.addEventListener('click', function() {
      const parentLi = this.parentElement;
      parentLi.classList.toggle('open');
    });
  });

  // Recursive filter function with toggleable matched items
  function filterListItems(element, searchTerm) {
    let matchFound = false;

    // Check direct children <li>
    const children = Array.from(element.children);
    children.forEach(child => {
      if (child.tagName === 'UL') {
        const grandChildren = Array.from(child.children);
        grandChildren.forEach(grandChild => {
          const matched = filterListItems(grandChild, searchTerm);
          matchFound = matchFound || matched;
        });
      }
    });

    const text = element.textContent.toLowerCase();
    const isMatch = text.includes(searchTerm);

    const shouldShow = isMatch || matchFound;
    element.style.display = shouldShow ? '' : 'none';
    element.classList.toggle('open', matchFound); // auto-expand matching

    return shouldShow;
  }

  // Search input handler
  searchBox.addEventListener('input', function() {
    const filter = this.value.trim().toLowerCase();
    const allRootItems = document.querySelectorAll('.shopMenuList > li');

    allRootItems.forEach(item => {
      filterListItems(item, filter);
    });

    // Toggle visibility of children if any match
    document.querySelectorAll('.shopMenuList li.has-children').forEach(li => {
      const hasVisibleChild = li.querySelector('ul > li:not([style*="display: none"])');
      if (hasVisibleChild) {
        li.classList.add('open');
      } else {
        li.classList.remove('open');
      }
    });

    // Collapse all if search is empty
    if (filter === '') {
      document.querySelectorAll('.shopMenuList li.has-children').forEach(li => {
        li.classList.remove('open');
      });
    }
  });
});
/*class that gets added to toggle lists when clicked*/
.open{
  border: solid 1px;
  background: dodgerblue;  
}
<input type="text" id="menuSearch" placeholder="Search category...">
<div class="shopMenuCategory">
  <ul class="shopMenuList">
    <li class="has-children"><span>Ceramic Tiles</span>
      <ul>
        <li class="has-children"><span>Floor Tiles</span>
          <ul>
            <li class="has-children"><span>15x80</span>
              <ul>
                <li><span>Matte</span></li>
              </ul>
            </li>
            <li class="has-children"><span>15x90</span>
              <ul>
                <li><span>Matte</span></li>
              </ul>
            </li>
            <li class="has-children"><span>20x20</span>
              <ul>
                <li><span>Matte</span></li>
              </ul>
            </li>
            <li class="has-children"><span>20x100</span>
              <ul>
                <li><span>Matte</span></li>
              </ul>
            </li>
            <li class="has-children"><span>20x120</span>
              <ul>
                <li><span>Matte</span></li>
              </ul>
            </li>
            <li class="has-children"><span>30x30</span>
              <ul>
                <li><span>Matte</span></li>
                <li><span>Glossy</span></li>
                <li><span>Rough</span></li>
              </ul>
            </li>
            <li class="has-children"><span>40x40</span>
              <ul>
                <li><span>Glossy</span></li>
                <li><span>Matte</span></li>
                <li><span>Rough</span></li>
              </ul>
            </li>
            <li class="has-children"><span>60x60</span>
              <ul>
                <li><span>Glossy</span></li>
                <li><span>Matte</span></li>
                <li><span>Rough</span></li>
              </ul>
            </li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</div>

when i search a parent list it will only display the matched parent list and the parent of that parent list which is correct… and then when i clicked the matched parent list it will not toggle the children is the issue..

example problem

ceramic tiles
    floor tiles
        30x40 - (searched list)
            matte - (it doesnt display when the search list is toggled)

correct way

ceramic tiles
    floor tiles
        30x40 - (searched list) then (clicked to toggle the child)
            matte - (it should display)

document.activeElement.blur() behavior in firefox and chrome

Create simple html page:

<p><input type="text" /></p>
<p><input type="text" /></p>
<p><input type="text" onfocus="document.activeElement.blur();" /></p>
<p><input type="text" /></p>
<p><input type="text" /></p>

Focus on first field and click tab on your keyboard to go to the next field.

In Firefox once you reach third field, you will not go further and you will be “stuck” on second input.

In Chrome, you will skip to the fourth input field and then fifth.

How can I prevent that? I would like to either reset activeElement and start from first input field, or even have functionality from Firefox – just stop from going further.

Fiddle if you want to play with it:

https://jsfiddle.net/1r9c2abn/1/

Why is my p5.play.js not working on github but working on VScode?

Whenever I try to deploy my p5play code on GitHub Pages, it just shows a blank screen. What can I do to resolve this issue? It works perfectly on VS Code. Specifically, the page has a white background. I believe the initial code is rendering, but my game code is not. Has anyone had this experience before? I tried using a popular, recommended alternative, and it did not work either.

Website link: https://vero0888888.github.io/FeverDream/
Repository link: https://github.com/Vero0888888/FeverDream

Testing classList.contains before attempting classList.remove?

A couple days prior to this post, I asked a question about iterating children of a node versus using querySelectorAll and one of the comments pointed out that the example I made wasn’t completely fair because when I removed a class from each child element of the parent, I didn’t first test that the child’s classList contained the class to be removed.

I thought that I read (but cannot now locate the source) on MDN that this was not beneficial because the remove method would just repeat what the contains method does. My impression was that it was less efficient to do so, which, of course, differs from the comments to the mentioned question. The accepted answer to this 9+ year-old question, appears to be in agreement with what I thought I had read, stating that:

Explicitly checking is pointless. It is a waste of time and bloats your code.

However, I modified the JS Bench example (modified version https://jsben.ch/LSbrl) that was provided in a comment to my question to compare the following for that same example; and it appears that testing using contains before attempting remove is about three times quicker than just executing remove.

const c = p.children;
const l = c.length;
for (let i = 0; i < l; i++) {
  c[i].classList.remove('sel');
}
// versus
const c = p.children;
const l = c.length;
for (let i = 0, cL; i < l; i++) {
  cL = c[i].classList;
  if( cL.contains('sel') ) cL.remove('sel');
}

My question is why? Does this indicate that the two methods do not use the same code such that remove is not contains with the added step of removing the class if it is found; but contains uses other data to make the determination and in a quicker manner?

The accepted answer to my recent question states that querySelectorAll “Uses highly optimized selector matching … “. Does contains make use of the same?

There appears to be a similar relationship between the element.matches() and element.closest() methods. I asked a question about two years ago also but didn’t know enough to ask it correctly.

In many instances, I’d expect that it makes little difference and won’t be noticeable, but it would be nice to understand a bit better. Perhaps I am just slow to catch on, but I would not have expected “if contains then remove” to be quicker than just remove, from reading the documentation on MDN. And it may be an instance of early optimization that is not necessary until an issue arises.

Thank you.

how to display chapters on Youtube Using time codes?

So recently the goal was to set up Youtube chapters on the Youtube Video and the guy on the internet said you have to put timecodes with this format starting from 0:00 description etc.

this is our description over here.

This video is based on a curated/reverse prompted engineered response that was asked to Chat GPT. Instead of asking Chat GPT how to do x, we asked what projects/track would you have to go through that by going through it by default you would.

0:00 Intro to how to make clickable box in HTML
2:55 Collaborative Learning endeavors
10:00 Future Projects Overview

The question is it doesn’t show in the timeline bar though.. enter image description here

and this is the description here bellow..

enter image description here

this is the link also
https://www.youtube.com/watch?v=zYQWP8kYGOA

Chrome Omnibox suggestions not showing when async response is delayed

I’m implementing a Chrome extension using chrome.omnibox.onInputChanged.addListener with debounce and abort control. When the async operation (getKeywordSuggestions()) takes too long (5-10 seconds) due to I
need to connect with an AI server, the suggest() callback executes without errors but fails to display the dropdown.

Code are showing below.

const DEBOUNCE_DELAY = 300;
let omniboxTimer, abortController, latestInput;

chrome.omnibox.onInputChanged.addListener((input, suggest) => {
  clearTimeout(omniboxTimer);
  if (abortController) abortController.abort();
  
  abortController = new AbortController();
  latestInput = input;
  
  if (!input.trim() || input.trim().length < MIN_LENGTH) return;
  
  setDefaultSuggestion(input);
  
  omniboxTimer = setTimeout(async () => {
    if (input !== latestInput) {
      console.log(`Input changed - discarding`);
      return;
    }

    try {
      const suggestions = await getKeywordSuggestions(input); // Network call
      if (input === latestInput) {
        const formatted = suggestions.map(s => ({
          content: escapeXML(s),
          description: `${escapeXML(s)} - AI suggestion`
        }));  // still ok here
        suggest(formatted); // Not showing suggestion box
      }
    } catch (error) {
      if (input === latestInput) {
        setDefaultSuggestion('Timeout error');
        suggest([]);
      }
    }
  }, DEBOUNCE_DELAY);
});

Key observations:

The network request completes successfully

console.log confirms valid suggestions are generated

No errors appear in Chrome’s extension console

The issue only occurs when response time exceeds ~3 seconds.

What I’ve tried:

Verified latestInput check works

Find that it can work properly when I run getKeywordSuggestions() within 3s.

Environment:

Manifest V3

What I expect is that, even when there is a long wait for a response (more than 5 seconds), the “suggest” function can still operate normally.

Clicking through multiple divs in a cycle

im making a page which functions like a visual novel -esque sequence. there are four stages to it, in each you click anywhere on the page and it changes to the next stage. on the last one clicking anywhere redirects to another page on the site.
after some research, i found this post and felt like the solution was perfect.
one issue though: it messes up the centering of the image (but only the first time it shows up and not the subsequent times???) for some odd reason and i have no idea how to fix it.

i tried changing “block” to “flex” (the verse’s original display is flex) but it defaults to flex-direction:row; instead of whats specified in the css (column)?

const text = document.querySelector(".banner")
document.addEventListener('click', myFunction);

let verses = document.querySelector("#verses").children
let count = 0
function myFunction() {
  Array.from(verses).forEach(el=> el.style.display="none")
  if(count < verses.length){
    verses[count].style.display = 'block'
    count ++
    if(count===verses.length) count =0
  } 
}
//code from https://stackoverflow.com/questions/71816135/how-to-iterate-with-click-event-through-array-of-divs
body {
    background: #000000;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 1200px;
  }

ul {
   list-style-position: outside;
   list-style-type: "* ";
   margin-left:20px;
    width:100%;
}

verses{
     display: flex;
    background-color:black;
    flex-direction: column;
    justify-content:center;
    align-items: center;
    border:;
    min-height:100px;
    width:1000px;
    margin:auto;
    image-rendering: pixelated;
    gap:0px;
}

.verse1{
     display: flex;
    background-color:black;
    flex-direction: column;
    justify-content:center;
    align-items: center;
    border:;
    min-height:100px;
    width:100%;
    margin:auto;
    image-rendering: pixelated;
    gap:0px;
}

.verse2{
     display: flex;
    background-color:black;
    flex-direction: column;
    justify-content:center;
    align-items: center;
    border:;
    min-height:100px;
    width:100%;
    margin:auto;
    image-rendering: pixelated;
    gap:0px;
}

.storygraphic1{
    background-image: url("https://file.garden/ZqzaaX6Iv3i3b03H/revamp/nmnsprite.PNG");
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain; 
    min-height: 500px;
    width:80%;
    image-rendering: pixelated;
}

.storygraphic2{
    background-image: url("https://file.garden/ZqzaaX6Iv3i3b03H/revamp/nmnsprite.PNG");
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain; 
    min-height: 500px;
    width:80%;
    image-rendering: pixelated;
}

.storytextbox{
    font-family: UTDRmono;
    color:white;
    width:700px;
    border:solid white 4px;
    padding:15px;
    background-color: black;
    min-height:70px;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    align-items: center;
}
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <link rel="stylesheet" href="WTF.css" />
        <script src="call_out.js"></script>
    </head>
    <body>
        
        
            
                <div class="banner">
                    <script src="call_out.js"></script>
                </div>
        
              <div id="verses"> 
                  
                    <div class="verse1">
                  
                        <div class="storygraphic1">
                        </div>
                    
                        <div class="storytextbox">
                            
                             <ul>
                                <li>text1</li>
                                <li>text2</li>
                            </ul> 

                        </div>
                    
                        
                    </div> <!--  verse closes  -->
                  
                  <div class="verse2" style="display: none">
                  
                        <div class="storygraphic2">
                        </div>
                    
                        <div class="storytextbox">
                            
                             <ul>
                                <li>TEXT3</li>
                                <li>TEXT4</li>
                            </ul> 

                        </div>
                   
                        
                    </div> <!--  verse closes  -->
                  
                </div> 
                
  
    </body>
</html>

QZ Tray fails to get signature and connecting

I’ve been trying to use my own certificate and private key but when I tried to implement setCertificatePromise() and setSignaturePromise() methods, my qz.websocket.connect() method does not resolve and stops my Vue App. I don’t know if I’m forgetting to implement or run other method. this all my code:

import apiClient from "./apiClient"
import { signService } from "./signService"
qz.security.setCertificatePromise(()=>{
    return signService.getCertificate()
    .then(response=>{
        const cert=response.data
        console.log(cert)
        return cert
    }).catch(err=>{
        console.error('Error en la obtención del certificado', err)
        throw err
    })
})
qz.security.setSignatureAlgorithm("SHA512");
qz.security.setSignaturePromise(function(toSign){
    console.log("Se inicia la firma del mensaje")
    return signService.signMessage(toSign)
    .then(response=>{
        console.error("message", response.data)
        return response.data
    })
    .catch(err=>{
        console.error("Error en la firma:",err)
        throw err
    })
})
qz.websocket.connect().then(()=>{
    console.log('conexion establecida')
}).catch((err)=>{
    console.error('error', err)
})
export const print=async(info)=>{
    const ESC='x1B'
    const GS='x1D'
    const alignCenter=ESC+'ax01'
    const alignLeft=ESC+'ax00'
    const alignRight=ESC+'ax02'
    const boldOn=ESC+'Ex01'
    const boldOff=ESC+'Ex00'
    const doubleHeightWidth=GS+'!x11'
    const normalSize=GS+'!x00'
    const cortar=GS+'Vx00'
    try{
        console.log('iniciar impresion')
        const printerName='POS-58'
        const config=qz.configs.create(printerName, {
            encoding:'Cp437'
        })
        console.log('creacion de ticket')
        let ticket="nn"
        ticket+=alignCenter
        ticket+=boldOn+doubleHeightWidth
        ticket+="Casa Paraíson"
        ticket+=normalSize+alignLeft
        ticket+="Dirección: Camelinas 12, Centro, 76750nTequisquiapan, Qro.nn"
        ticket+="Numero de cuenta: "+info.id+"nCliente: "+info.name+"nn"
        ticket+="Lista de productos:n-------------------n"
        ticket+="Nombre del producto   |  Precion"+boldOff
        info.products.forEach((prod)=>{
            ticket+=`${prod.product.name.padEnd(20)}$${prod.product.price.toFixed(2)}n`
        })
        ticket+=boldOn+"-------------------n"+boldOff
        ticket+=alignRight
        
        if(info.status==='open'){
            ticket+="Subtotal: $"+boldOn+info.subtotal.toFixed(2)+boldOff+"n"
            ticket+=alignCenter+doubleHeightWidth
            ticket+="Pre-ticketnnnn"+cortar
        }else{ 
            if(info.tip!==0){
                ticket+="Subtotal: $"+boldOn+info.subtotal.toFixed(2)+boldOff+"n"
                ticket+="Propina: $"+boldOn+info.tip.toFixed(2)+boldOff+"n"
            }
            ticket+="Total:"
            ticket+=boldOn+`$${info.total.toFixed(2)}nn`
            ticket+=alignCenter+doubleHeightWidth
            ticket+="Gracias pornsu compra!nnnn"+cortar
        }
        console.log('inicio con la firma')
        const data=[{type:'raw', format:'plain', data:ticket}]
        console.log("inicio de la impresion fisica")
        await qz.print(config, data)
        console.log("fin de la impresion fisica")
        return true
    }catch(err){
        console.error('Error en la impresion', err)
        return false
    }
}

setCertificatePromise() runs successfully, but after that nothing happens, any error, any confirmation, nothing.
“conexion establecida” message never appears in console.

What can I do?

TypeError: Api.payments.SendStars is not a constructor when sending Telegram Stars gift with GramJS (telegram.js)

I’m trying to write a NodeJS script using the telegram library (v2.x) to buy a Telegram Stars gift for my own user account.

I can successfully connect, log in, and fetch the list of available gifts using Api.payments.GetStarGifts. The problem occurs when I try to buy a gift using Api.payments.SendStars.

Here is a minimal, reproducible example of my code:

import "dotenv/config";
import { TelegramClient, Api } from "telegram";
import { StringSession } from "telegram/sessions/index.js";
import fs from "fs/promises";

// --- CONFIGURATION ---
// These are loaded from a .env file
const API_ID = Number(process.env.API_ID);
const API_HASH = process.env.API_HASH;
const SESSION_FILE = "./my_session.txt";

// --- MAIN SCRIPT ---
(async () => {
    // 1. Initialize session from file
    let sessionString = "";
    try {
        sessionString = await fs.readFile(SESSION_FILE, { encoding: "utf-8" });
    } catch {
        console.log("Session file not found. A new one will be created after login.");
    }

    const client = new TelegramClient(
        new StringSession(sessionString),
        API_ID,
        API_HASH,
        { connectionRetries: 5 }
    );

    // 2. Connect and save the session if it's new
    await client.connect();
    
    // This will prompt for login only if the session is invalid or missing
    if (!await client.isAuthenticated()) {
         // Handle login logic here (omitted for brevity, assume session is valid)
         console.error("Session is not valid. Please generate a session first.");
         return;
    }
    
    const me = await client.getMe();
    console.log(`Logged in as: ${me.firstName} (@${me.username})`);

    // 3. Fetch the list of gifts (this part works correctly)
    console.log("Fetching gift list...");
    const allGifts = await client.invoke(new Api.payments.GetStarGifts({}));
    const unlimitedGifts = allGifts.gifts.filter((g) => g.availabilityRemains == null);

    if (!unlimitedGifts.length) {
        console.log("No unlimited gifts found.");
        return;
    }
    const giftToBuy = unlimitedGifts[0]; // Let's just buy the first one
    console.log(`Attempting to buy gift ID: ${giftToBuy.id} for ${Number(giftToBuy.stars)} stars.`);

    // 4. Attempt to send the gift (this is where the error occurs)
    try {
        const request = new Api.payments.SendStars({
            peer: new Api.InputPeerUser({
                userId: me.id,
                accessHash: me.accessHash,
            }),
            gift: true,
            id: giftToBuy.id,
            randomId: BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)),
        });
        
        // The error is thrown on the next line
        await client.invoke(request);

        console.log("Gift purchased successfully!");
    } catch (error) {
        console.error("Failed to purchase gift:", error);
    }

    await client.disconnect();
})();

To reproduce the issue, you need a valid session string. I am generating it with a separate script and saving it to my_session.txt.

When I run this code, the script successfully logs in and fetches the gift list, but then it crashes with the following error:

Attempting to buy gift ID: 5170145012310081615 for 15 stars.
Failed to purchase gift: TypeError: Api.payments.SendStars is not a constructor
    at file:///path/to/your/project/index.js:63:25
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

I’ve double-checked the official MTProto documentation for payments.sendStars and the parameters seem correct. The GetStarGifts constructor works fine, so the Api object is being imported correctly.

What is the correct way to construct and invoke Api.payments.SendStars to send a gift to myself using a user client with the telegram library?

Why does await headers() return an empty HeadersList in Next.js 15 Server Component?

I’m using Next.js 15 and trying to access request headers in a React Server Component inside the app/ directory using the new async headers() API introduced in this version.

Here’s the start of my page.tsx:

// app/users/page.tsx

import { headers } from 'next/headers'

export const dynamic = 'force-dynamic'

const UsersPage = async () => {
  const headersList = await headers()
  console.log(headersList)
  return <div>Users Page</div>
}

export default UsersPage

However, the headersList I get is always empty:

HeadersList {
  cookies: null,
  [Symbol(headers map)]: Map(0) {},
  [Symbol(headers map sorted)]: null
}

The text streaming in Nodejs getting slower in production

I am trying to make a chatbot using NodeJS and NextJS, but the text stream is getting slower in production.
This snippet contains an Express, Node.js project for streaming chat messages to the client. It is working fast in localhost, but in production, it is streaming text slowly.
I have set proxy_buffering off in NGINX, but it is still slower than localhost.

export async function getChatStream(
  req: RequestWithUserAuthInfo,
  res: Response,
  next: NextFunction
): Promise<void> {
  try {
    const query = req.query?.q as string;
    const params = req.params;
    const sessionId = params.id;
    const userId = req.user.id;

    if (!query || !sessionId) {
      throw {
        status: 400,
        message: "Query and SessionId are required",
      };
    }

    if (!userId) {
      throw {
        status: 400,
        message: "User id is required",
      };
    }
    console.log("Incoming request:", { userId, sessionId, query });

    const session = await findOrCreateSession(userId, sessionId, query);

    await saveMessage(session, query, MessageSender.USER);

    // Set up SSE headers for real-time streaming
    res.setHeader("Content-Type", "text/event-stream");
    res.setHeader("Cache-Control", "no-cache");
    res.setHeader("Connection", "keep-alive");

    let botResponse = "";
    let messageId = "uuid";

    let clientDisconnected = false;

    req.on("close", () => {
      clientDisconnected = true;
      console.log("Client disconnected. Aborting stream.");
    });

    // Stream bot response in chunks
    for await (const chunk of getStreamFromAPI(query, sessionId)) {
      if (clientDisconnected) break;
      botResponse += chunk;
      res.write(`${chunk}`);
    }

    await saveMessage(
      session,
      botResponse,
      MessageSender.BOT,
      messageId,
      false
    );

    res.end();
  } catch (error) {
    next(error);
  }
}

Group elements in a grid and apply a border around a group of elements

I have the following grid of 0s and 1s (image 1). I want to be able to group each section together so that when the user hovers over a section it will make a border around the entire section (image 2). The sections can be from different lines (image 3).

Image 1

1s and 0s in a grid

Image 2

1s and 0s in a grid with a border around a section

Image 3

1s and 0s in a grid with a border around a section in multiple lines

Here are my questions:

  1. How do I structure the HTML?
  2. How do I do the CSS?
  3. What JavaScript, if any, needs to be added?

Here is what I thought of:

Method 1
Each 1 or 0 will be a div and use a div to group the 1s and 0s and use a div to group the entire thing such as the following:

<div>
    <div>
        <div>0</div>
    </div>
    <div>
        <div>0</div>
    </div>
    <div>
        <div>0</div>
        <div>1</div>
        <div>0</div>
    </div>
    <div>
        <div>0</div>
    </div>
</div>

That would be easier to style the border, but how would I still maintain the grid shape?

Method 2
Each 1 or 0 will be a div and each group will have the same class with each other such as the following:

<div>
    <div data-group0>0</div>
    <div data-group1>0</div>
    <div data-group2>0</div>
    <div data-group2>1</div>
    <div data-group2>0</div>
    <div data-group3>0</div>
    <div data-group4>1</div>
</div>

This would be easier to style the grid shape, but how would I style the borders?

How to call an API on before unload event in react

How to call an API on before unload event in react, I have tried fetch with Keepalive true & sendbecon(doesn’t support authorization headers in headers)

Also, I want to show dialog box with custom message on unload. the behavior is in consistent.

  // Handle browser tab close or refresh
  useEffect(() => {
    const handleBeforeUnload = (e) => {
      e.preventDefault();
      e.returnValue = '';
     // unlock user API call
      unlock();
    };
    window.addEventListener('beforeunload', handleBeforeUnload);
    return () => window.removeEventListener('beforeunload', handleBeforeUnload);
  }, []);