Countdown react hook

I’m trying to build a react hook that does a simple stateful second count down.

Example use case:

Rate limit (client-side) a user when requesting a new SMS code.

Usage:

const { cooldown, startCooldown } = useCooldown();

<button disabled={cooldown > 0}>Resend Code {`(${cooldown ? cooldown : ''})`}</button>

Hook code:

import { useState, useRef, useEffect } from 'react';

export default function useCooldown(cooldownTime = 10) {
  const [cooldown, setCooldown] = useState(0);
  const intervalRef = useRef(null);

  function updateTimer() {
    console.log('cooldown remaining: ', cooldown);
    setCooldown(cooldown - 1);
    if (cooldown === 0) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
  }

  function startCooldown() {
    console.log('starting cooldown from: ', cooldownTime);
    setCooldown(cooldownTime);
    intervalRef.current = setInterval(updateTimer, 1000);
  }

  useEffect(() => {
    return () => {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    };
  }, []);

  return { cooldown, startCooldown };
}

The browser console stops at

starting cooldown from:  10

I’m thinking since cooldown is stateful, the parent component gets reloaded along with a new useCooldown() instance. How can this be solved?

Flashing text in function called every frame JS

I’m developing an addon for this project: https://jcm.joban.org/latest/dev/scripting/pids/ (It uses a version of javascript to create screens in-game. In the scripts, most code is run in a function that is called every frame. Within this function, I want to make a text object that flashes. Ideally, I want a solution that doesn’t change the flash interval based on the framerate, but setInterval() and setTimeout() aren’t available in the mod’s implementation of JS. What other ways should I try to make text flash within this function?

API call returns HTML (ERR_NGROK_6024) instead of JSON response

I’m building a small React,TypeScript,Axios app for encrypted notes.

  • Frontend: React (with Axios)

  • Backend: Node/Express on http://localhost:3000

  • Tunnel: ngrok (ngrok http 3000)

Everything works fine locally, but when I use ngrok, some requests return an HTML page from ngrok instead of the JSON response from my backend.

src/api/notes.ts

import axios from "axios";

const api = axios.create({
    baseURL: "https://efaed12151ea.ngrok-free.app",
    headers: {
        "Content-Type": "application/json",
    },
});

export type NotePayload = {
    encryptedContent: string;
    encryptedIv: string;
};

export type CreateNoteResponse = {
    data: {
        id: string;
    };
};

export type GetNoteResponse = {
    data: {
        id: string;
        encryptedContent: string;
        encryptedIv: string;
    };
};

export async function createNote(payload: NotePayload): Promise<string> {
    const response = await api.post<CreateNoteResponse>("/notes", payload);
    return response.data.data.id;
}

export async function getNote(id: string): Promise<GetNoteResponse["data"]> {
    const response = await api.get<GetNoteResponse>(`/notes/${id}`);
    console.log(response);
    return response.data.data;
}

src/pages/CreateNote.tsx

import React, { useState } from "react";
import { encrypt, generateShortKey } from "../utils/crypto";
import { createNote } from "../api/notes";
import NoteForm from "../components/NoteForm";
import NoteResult from "../components/NoteResult";


export default function CreateNote() {
    const [content, setContent] = useState("");
    const [shortKey, setShortKey] = useState<string | null>(null);
    const [link, setLink] = useState<string | null>(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState<string | null>(null);

    const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();

        if (!content.trim()) {
            setError("Note content cannot be empty.");
            return;
        }

        setError(null);
        setLoading(true);

        try {
            const key = generateShortKey();
            const { ciphertext, iv } = await encrypt(content, key);

            const id = await createNote({
                encryptedContent: ciphertext,
                encryptedIv: iv,
            });

            setShortKey(key);
            setLink(`${window.location.origin}/${id}#${key}`);
        } catch (err: any) {
            console.error("Failed to create note:", err);
            setError("Failed to create note. Please try again.");
        } finally {
            setLoading(false);
        }
    }

    const handleReset = () => {
        setContent("");
        setShortKey(null);
        setLink(null);
        setError(null);
    };

    const copyLink = async () => {
        if (link) {
            try {
                await navigator.clipboard.writeText(link);
                alert("Link copy!");
            } catch (err) {
                console.error("Failed to copy link:", err);
            }
        }
    };

    return (
      <div className="p-6 mx-auto my-8 max-w-lg border rounded-lg shadow-lg bg-white">

            <NoteForm
              content={content}
              onChange={setContent}
              onSubmit={handleSubmit}
              loading={loading}
              error={error}
            />

          {link && (
            <NoteResult link={link} onCopy={copyLink} onReset={handleReset} />
          )}
      </div>
    );
}

src/pages/ViewNote.tsx (simplified)

useEffect(() => {
  const shortKey = window.location.hash.slice(1);

  if (!id || !shortKey) {
    navigate("/404");
    return;
  }

  const load = async () => {
    try {
      const { encryptedContent, encryptedIv } = await getNote(id);
      const decrypted = await decrypt(encryptedContent, encryptedIv, shortKey);
      setNote(decrypted);
    } catch (err) {
      console.error("Error fetching note:", err);
      navigate("/404");
    } finally {
      setLoading(false);
    }
  };

  load();
}, [id]);

Console.log(response) write FAIL:

{data: '<!DOCTYPE html>n<html class="h-full" lang="en-US" …EiLCJ0aXRsZSI6Ik9LIn0="></div>n </body>n</html>n', status: 200, statusText: '', headers: AxiosHeaders, config: {…}, …} config : {transitional: {…}, adapter: Array(3), transformRequest: Array(1), transformResponse: Array(1), timeout: 0, …} data : "<!DOCTYPE html>n<html class="h-full" lang="en-US" dir="ltr">n <head>n <link rel="preload" href="https://cdn.ngrok.com/static/fonts/euclid-square/EuclidSquare-Regular-WebS.woff" as="font" type="font/woff" crossorigin="anonymous" />n <link rel="preload" href="https://cdn.ngrok.com/static/fonts/euclid-square/EuclidSquare-RegularItalic-WebS.woff" as="font" type="font/woff" crossorigin="anonymous" />n <link rel="preload" href="https://cdn.ngrok.com/static/fonts/euclid-square/EuclidSquare-Medium-WebS.woff" as="font" type="font/woff" crossorigin="anonymous" />n <link rel="preload" href="https://cdn.ngrok.com/static/fonts/euclid-square/EuclidSquare-Semibold-WebS.woff" as="font" type="font/woff" crossorigin="anonymous" />n <link rel="preload" href="https://cdn.ngrok.com/static/fonts/euclid-square/EuclidSquare-MediumItalic-WebS.woff" as="font" type="font/woff" crossorigin="anonymous" />n <link rel="preload" href="https://cdn.ngrok.com/static/fonts/ibm-plex-mono/IBMPlexMono-Text.woff" as="font" type="font/woff" crossorigin="anonymous" />n <link rel="preload" href="https://cdn.ngrok.com/static/fonts/ibm-plex-mono/IBMPlexMono-TextItalic.woff" as="font" type="font/woff" crossorigin="anonymous" />n <link rel="preload" href="https://cdn.ngrok.com/static/fonts/ibm-plex-mono/IBMPlexMono-SemiBold.woff" as="font" type="font/woff" crossorigin="anonymous" />n <link rel="preload" href="https://cdn.ngrok.com/static/fonts/ibm-plex-mono/IBMPlexMono-SemiBoldItalic.woff" as="font" type="font/woff" crossorigin="anonymous" />n <meta charset="utf-8">n <meta name="author" content="ngrok">n <meta name="description" content="ngrok is the fastest way to put anything on the internet with a single command.">n <meta name="robots" content="noindex, nofollow">n <meta name="viewport" content="width=device-width, initial-scale=1">n <link id="style" rel="stylesheet" href="https://cdn.ngrok.com/static/css/error.css">n <noscript>You are about to visit efaed12151ea.ngrok-free.app, served by xx.xxx.xx.xxx. This website is served for free through ngrok.com. You should only visit this website if you trust whoever sent the link to you. (ERR_NGROK_6024)</noscript>n <script id="script" src="https://cdn.ngrok.com/static/js/error.js" type="text/javascript"></script>n </head>n <body class="h-full" id="ngrok">n <div id="root" data-payload="eyJjZG5CYXNlIjoiaHR0cHM6Ly9jZG4ubmdyb2suY29tLyIsImNvZGUiOiI2MDI0IiwiaG9zdHBvcnQiOiJlZmFlZDEyMTUxZWEubmdyb2stZnJlZS5hcHAiLCJtZXNzYWdlIjoiWW91IGFyZSBhYm91dCB0byB2aXNpdCBlZmFlZDEyMTUxZWEubmdyb2stZnJlZS5hcHAsIHNlcnZlZCBieSA5My4xNTkuNDYuMTMxLiBUaGlzIHdlYnNpdGUgaXMgc2VydmVkIGZvciBmcmVlIHRocm91Z2ggbmdyb2suY29tLiBZb3Ugc2hvdWxkIG9ubHkgdmlzaXQgdGhpcyB3ZWJzaXRlIGlmIHlvdSB0cnVzdCB3aG9ldmVyIHNlbnQgdGhlIGxpbmsgdG8geW91LiIsInNlcnZpbmdJUCI6IjkzLjE1OS40Ni4xMzEiLCJ0aXRsZSI6Ik9LIn0="></div>n </body>n</html>n" headers : AxiosHeaders {content-length: '2878', content-type: 'text/html'} request : XMLHttpRequest {onreadystatechange: null, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …} status : 200 statusText : "" [[Prototype]] : Object

What works

  • POST /notes (creating a note) works both locally and via ngrok.

  • GET http://localhost:3000/notes/:id → returns the expected JSON:

{
  "id": "h7KH5MjnxY7",
  "encryptedContent": "...",
  "encryptedIv": "..."
}

What doesn’t work

  • GET https://efaed12151ea.ngrok-free.app/notes/:id → sometimes returns JSON, but returns an HTML page from ngrok instead.

Example log from console.log(response) in Axios:

{
  "status": 200,
  "headers": { "content-type": "text/html" },
  "data": "<!DOCTYPE html><html ... (ERR_NGROK_6024) ... </html>"
}

So Axios sees a 200 OK, but the response is an HTML warning page from ngrok, not JSON.


What I tried

Manually checked in browser:

  • https://efaed12151ea.ngrok-free.app/notes/:id → often HTML (ngrok error page)

    Curl tests:

    curl -i https://efaed12151ea.ngrok-free.app/notes/h7KH5MjnxY7   # returns HTML with ERR_NGROK_6024
    
  • Tried with /api/notes/:id as well — same behavior.


Why does ngrok return its own HTML page (ERR_NGROK_6024) instead of forwarding my backend’s JSON response?

  • Is this a limitation of ngrok free tunnels (idle timeout, etc.)?

  • Do I need to change how Axios calls the API (baseURL, headers, etc.)?

  • Is there a reliable way to ensure my frontend always gets JSON from ngrok?

But when I open "/ID#shortKey", instead of JSON I get an ngrok HTML page.
In console.log(response) I see status: 200, but response.data contains an HTML string (<!DOCTYPE html> ...), not JSON.

I changed some variables, also rewrote the code itself more than once. Output in the console.log id, shortkey, etc.
The backend is fine. Now I can’t get NGROK to work. Sorry if it’s unclear, this is the first time I’ve asked for help on this site.

How can I use the input search in a different component using Algolia/Vue

I am using the infinite scroll with Vue 3 and InstantSearch(Algolia) but the

<ais-search-box placeholder="Search here…" class="searchbox" />

it should be in a different component, because due to the organization of my Vue components and blade components(I am using Laravel), the input search must be in a different place. the input and the results must be in different components. What can I do?

Posts.vue

<template>
  <div class="container">
      <ais-instant-search
        :search-client="searchClient"
        index-name="posts"
        :insights="true"
        :future="{ preserveSharedStateOnUnmount: true }"
      >
        <div class="search-panel">
          <div class="search-panel__results">
            <ais-search-box placeholder="Search here…" class="searchbox" />
            <app-infinite-hits>
              <template #item="{ item }">
                <h1><ais-highlight :hit="item" attribute="title" /></h1>
                <p><ais-highlight :hit="item" attribute="slug" /></p>
              </template>
            </app-infinite-hits>
          </div>
        </div>
      </ais-instant-search>
    </div>
</template>

<script>
import algoliasearch from 'algoliasearch/lite';
import AppInfiniteHits from '../../Components/InfiniteHits.vue';

export default {
  components: { AppInfiniteHits },
  data() {
    return {
      searchClient: algoliasearch(
        'XXXXXXX',
        'XXXXXXXXXXXXXXXXXX'
      ),
    };
  },
};
</script>

and this is the InfiniteHits.vue component:

<template>
  <ol v-if="state">
    <li v-for="hit in state.items" :key="hit.objectID">
      <slot name="item" :item="hit"></slot>
    </li>
    <li class="sentinel" v-observe-visibility="visibilityChanged"></li>
  </ol>
</template>

<script>
import { createWidgetMixin } from 'vue-instantsearch/vue3/es';
import { connectInfiniteHits } from 'instantsearch.js/es/connectors';

export default {
  mixins: [createWidgetMixin({ connector: connectInfiniteHits })],
  methods: {
    visibilityChanged(isVisible) {
      if (isVisible && !this.state.isLastPage) {
        this.state.showMore();
      }
    },
  },
};
</script>

<style scoped>
.sentinel {
  list-style-type: none;
}
</style>

these are the packages I am using:

"algoliasearch": "^4.25.2"
"vue-instantsearch": "^4.21.3",
"vue-observe-visibility": "^1.0.0"
"vite": "^5.2.6",
"vue": "^3.5.18"

CSS animation flickering

I’m, creating the marquee of banners for my Shopify store, here is the code:

      <div class="site-header__banners-wrapper">
        <div class="site-header__banners">
          {% for header_banner in shop.metafields.custom.header_banners.value %}
            <div class="site-header__banner">
              {% assign banner_url = shop.metafields.custom.header_banner_urls.value[forloop.index0] %}
              {% if banner_url %}
                <a href="{{ banner_url }}">
                  <img src="{{ header_banner | image_url }}" alt="">
                </a>
              {% else %}
                <img src="{{ header_banner | image_url }}" alt="">
              {% endif %}
            </div>
          {% endfor %}
        </div>
      </div>
      <style>
        .site-header__banners-wrapper {
          overflow: hidden;
        }
        .site-header__banners-wrapper:hover .site-header__banners {
          animation-play-state: paused;
        }
        .site-header__banners {
          display: flex;
          flex-wrap: nowrap;
          animation: animation var(--duration) linear infinite;
          
        }
        .site-header__banner {
          flex: 0 0 auto;
        }
        @keyframes animation {
          from { 
            transform: translateX(0);
          }
          to {
            transform: translateX(var(--length))
          }
        }
      </style>
      <script>
        document.addEventListener('DOMContentLoaded', () => {
          const bannersWrapper = document.querySelector('.site-header__banners-wrapper');
          const bannersWrapperWidth = bannersWrapper.offsetWidth;

          const banners = document.querySelector('.site-header__banners');

          const bannersArray = Array.from(document.querySelectorAll('.site-header__banner'));

          let bannersWidth = bannersArray.reduce((bannersWidth, banner) => bannersWidth + banner.offsetWidth, 0);
          
          const duration = bannersWidth / 100;

          document.documentElement.style.setProperty('--duration', `${duration}s`);
          document.documentElement.style.setProperty('--length', `-${bannersWidth}px`);

          let index = 0;

          while (bannersWidth < bannersWrapperWidth * 2) {
            const cloneBanner = bannersArray[index % bannersArray.length].cloneNode(true);
            banners.appendChild(cloneBanner);
            bannersWidth += cloneBanner.offsetWidth;
            index++;
          }
        });
      </script>

However I have an issue with iPhone, every few seconds an animation blinks like a whole marquee disappears for a tiny fraction of a time.

You can check an issue there: https://sveikatospasaulis.lt/

Please help me to understand the reason and to fix it.

Three.js vers 133 trying to add SSAO and change renderer to composer

I’m trying to add SSAO to my three.js model, but when I change renderer.render() into composer.render() orbit control stops working and zoom of model switch to max.enter image description here
one image is before adding composer and another is after
enter image description here

here is my init function
func.init = (set) => {

    func.floorTexture = new THREE.TextureLoader().load('/images/grass.jpeg');   
    scissorTexture = new THREE.TextureLoader().load('/images/scissor3.png');

    func.data.selector = set.div_id;
    func.data.resizable = set.resizable;
    func.data.boundingSphere = set.initBoundingSphere;

    if (set.is_damping != undefined) func.data.is_damping = set.is_damping;

    func.data.persp_cam_angle_xy = set.cam_angle[0];
    func.data.persp_cam_angle_xz = set.cam_angle[1];

    func.data.win_width = jQuery(`#${func.data.selector}`).width();
    func.data.win_height = jQuery(`#${func.data.selector}`).height();

    func.data.renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
        logarithmicDepthBuffer: false,//true
        powerPreference: 'high-performance',
        precision: 'highp',
    });

    func.data.autoClear = true;//true
    func.data.renderer.setPixelRatio(window.devicePixelRatio);
    func.data.renderer.physicallyCorrectLights = true;
    func.data.renderer.outputEncoding = THREE.sRGBEncoding;
    func.data.renderer.sortObjects = true;
    func.data.renderer.shadowMap.enabled = true;
    func.data.renderer.shadowMap.type = THREE.PCFSoftShadowMap; //
    func.data.renderer.shadowMap.autoUpdate = false;
    func.data.renderer.setSize(func.data.win_width, func.data.win_height);
    func.data.renderer.domElement.classList.add(`${func.data.selector}-visualizer`);

    // 
    let canvasContainer = document.getElementById(func.data.selector);
    if (canvasContainer.querySelector(`.${func.data.selector}-visualizer`)) {

        canvasContainer.querySelector(`.${func.data.selector}-visualizer`).remove();

    }

    document.getElementById(func.data.selector).appendChild(func.data.renderer.domElement);

    func.data.scene = new THREE.Scene();
    func.ambientLight = new THREE.AmbientLight(0xffffff, 1.5);//3
    func.data.scene.add(func.ambientLight);
    func.data.renderer.setClearColor(set.background_color[0], set.background_color[1]);

    if (set.light_type[0] == "HemisphereLight") {
        let hemiSphere = new THREE.HemisphereLight(set.light_type[1], set.light_type[2], set.light_type[3]);
    }
    else if (set.light_type[0] == "AmbientLight") {
        let ambientLight = new THREE.AmbientLight(set.light_type[1], set.light_type[2]);
    }

    if (func.data.renderer.domElement.classList.contains(`${func.data.selector}-visualizer`) && !func.data.renderer.domElement.classList.contains('three-win-axis-visualizer') && !func.data.renderer.domElement.classList.contains('three-win-side-axis-visualizer') && typeof ThreePerf == 'function') {
        func.perf = new ThreePerf({
            showGraph: true,
            anchorX: 'right',
            anchorY: 'top',
            domElement: $(`#${func.data.selector}`).get(0), // or other canvas rendering wrapper
            renderer: func.data.renderer, // three js renderer instance you use for rendering
            scale: 1,
            guiVisible: false,
            actionToCallUI: "gui"
        });
        var isPerfVisiblity = localStorage.getItem(`${func.data.selector}-PerfomanceTableVisible`);
        if (isPerfVisiblity != 'false' && isPerfVisiblity != null)
            $(`#${func.data.selector}`).find('#three-perf-ui').removeClass('d-none');
        else
            $(`#${func.data.selector}`).find('#three-perf-ui').addClass('d-none');
    }
    func.data.camera = createPerspectiveCamera()
    func.data.controls = new THREE.OrbitControls(func.data.camera, func.data.renderer.domElement);
    // 1. Creating EffectComposer іand save it in func.data
    func.data.composer = new THREE.EffectComposer(func.data.renderer);
    func.data.composer.setSize(func.data.win_width, func.data.win_height);
    // 2. Add RenderPass (main scene render)
    const renderPass = new THREE.RenderPass(func.data.scene, func.data.camera);
    //renderPass.clear = true; 
    func.data.composer.addPass(renderPass);
    // 3. Create and setup SSAOPass
    const ssaoPass = new THREE.SSAOPass(
        func.data.scene,
        func.data.camera,
        func.data.win_width,
        func.data.win_height
    );
    ssaoPass.kernelRadius = 10;     // Effect raduis. Try values from 8 to 32
    ssaoPass.minDistance = 0.005;   // Min distance for effect
    ssaoPass.maxDistance = 0.1;     // Max distance
    func.data.composer.addPass(ssaoPass);

    //// 4. Add GammaCorrection for right visibility of colors
    const gammaPass = new THREE.ShaderPass(THREE.GammaCorrectionShader);
    //gammaPass.clear = false; 
    gammaPass.renderToScreen = true;
    func.data.composer.addPass(gammaPass);

    //var copyPass = new THREE.ShaderPass(THREE.CopyShader);
    //copyPass.renderToScreen = true;
    //func.data.composer.addPass(copyPass);

    // --- End implement SSAO ---

    func.addOrbitEvents(func.data.controls);
    if (func.data.is_damping) func.controlDamping();
    func.data.controls.addEventListener('change', requestRenderIfNotRequested);

    if (!set.is_isometric) func.initPerspectiveCamera('ortho');
    else func.initIsometricCamera('ortho');

    if (func.data.resizable) {

        window.addEventListener('resize', func.onWindowResize);
        window.addEventListener('resize', func.controlFunc);
    }

    boundingBoxConst = func.data.boundingSphere.radius;
};
and here is my render function
function requestRenderIfNotRequested() {
    if (!func.data.renderRequested) {
        func.data.renderRequested = true;
        requestAnimationFrame(render);
    }
};

function render() {
    //requestAnimationFrame(render);

    func.data.renderRequested = false;

    if (func.data.timeout !== null) clearTimeout(func.data.timeout);
    func.data.timeout = setTimeout(function () {
        func.controlFuncEnd();
    }, 100);
    //func.data.controls.update();

    // Check if controls zoomIn/Out
    let controls = func.data.controls;
    let camera = func.data.camera;
    let lastZoom = camera.zoom;
    let newDistance = camera.position.distanceTo(controls.target);
    let epsilon = 0.0001;

    func.isZoomingIn = false;
    func.isZoomingOut = false;

    if (camera.isPerspectiveCamera) {
        if (Math.abs(newDistance - lastDistance) > epsilon && visualizer) {
            func.isZoomingIn = newDistance < lastDistance;
            func.isZoomingOut = newDistance > lastDistance;
        }
        lastDistance = newDistance;
    }

    if (camera.isOrthographicCamera) {
        if (Math.abs(camera.zoom - lastZoom) > epsilon && visualizer) {
            func.isZoomingIn = camera.zoom > lastZoom;
            func.isZoomingOut = camera.zoom < lastZoom;
        }
        lastZoom = camera.zoom;
    }

    func.isZoomingInOut = func.isZoomingIn || func.isZoomingOut;

    func.data.controls.update();

    if (clippingMovePlaneState && clippingMovePlaneState()) {
        func.updateClippingPlane();
        updateLableSizes();
    }

    if (func.data.renderer.domElement.classList.contains(`${func.data.selector}-visualizer`) && !func.data.renderer.domElement.classList.contains('three-win-axis-visualizer') && !func.data.renderer.domElement.classList.contains('three-win-side-axis-visualizer') && typeof ThreePerf == 'function')
        func.perf.begin();

    //func.data.renderer.render(func.data.scene, func.data.camera);
    //if (func.data.composer) {
        func.data.composer.render();
    //} else {
    //  //if composer is not initted
    //  func.data.renderer.render(func.data.scene, func.data.camera);
    //}

    if (func.data.renderer.domElement.classList.contains(`${func.data.selector}-visualizer`) && !func.data.renderer.domElement.classList.contains('three-win-axis-visualizer') && !func.data.renderer.domElement.classList.contains('three-win-side-axis-visualizer') && typeof ThreePerf == 'function')
        func.perf.end();

    func.controlFunc();
    func.controlFuncStart();
    if (visualizer && (visualizer.toolIsActive('pick') || visualizer.toolIsActive('measure') || (visualizer.positionAssemblyManager && visualizer.positionAssemblyManager.mode == 'order')) && !func.data.isRotating) {
        if (func.isZoomingInOut) return;
        func.controlFuncEnd();
    }
};

I’ve also gismo cube that uses this function to init to and removed it, it want helped.
Also tried to add renderToScreen to the last pass of composer. Nothing helped me!

Uncaught ReferenceError: require is not defined, in my Vercel app

Problem

I have a React app using Leaflet.js (v.1.9.0) and I host it in Vercel. Building works OK, but when visiting my app online logs this message in console:

Uncaught ReferenceError: require is not defined.

Where it happens

The file & line happening is ...assets/index-BKsOcw9k.js:67 where index-BKsOcw9k.js is a build file inside the dist/ folder. Locating it the code, it has this: ...QT.exports=e(require("leaflet"));e... and the problem is in the require.

My search

Searching around and asking AI, I understand that there is a problem with ESM and Leaflet (maybe, Rollup is included in the problem). I thought upgrading to Leaflet v.2 where it says it has Fully ESM support and that may solve the problem but there are conflicts with other packages (e.g. leaflet-draw, leaflet-geoman, etc…).

I import leaflet in my main component like that:
import L from "leaflet";. I’ve also tried import * as L from "leaflet";. None of them working when re-deploying in Vercel.

Thanks in advance!

Whenever I select the dropdowns in iframe, it soft scrolls to the top of the form

In a React app, the sign-up component behaves correctly. However, when embedded in an iframe, choosing any option in a dropdown causes the viewport to scroll to the top of the form.

Here is the react code with MUI

      <Stack direction={{ xs: 'column', sm: 'row' }} alignItems="center" spacing={2}>
        <Stack width={1}>
          <Typography>How would you like to pay?</Typography>
        </Stack>
        <Stack width={1}>
          <Field.Select name="paymentMethod" label="Payment Method" required>
            {payments.map((option) => (
              <MenuItem key={option.id} value={`${option.id}::${option.name}`}>
                {option.name}
              </MenuItem>
            ))}
          </Field.Select>
        </Stack>
      </Stack>

It works well in react, but not working in iframe

Dialog closes when parent is rerendered in React/NextJS/Shadcn

I have the following page:

'use client';
import { use } from 'react';

import { useCourseNode } from '@/api/course/hooks';
import { Loader } from '@/components/loaders/loader';
import { DefaultTreeNodeBox } from '@/components/tree/renderers/boxes/node-box';
import { EntityTargetType } from '@/lib/enums';
import { useDocumentTitle } from '@uidotdev/usehooks';
import { notFound } from 'next/navigation';

export default function ContentPage(props: {
  params: Promise<{ name: string; node: string }>;
}) {
  const params = use(props.params);

  const queryNode = useCourseNode(params.node);

  useDocumentTitle(
    queryNode.data?.name
      ? queryNode.data.name + ' | ' + queryNode.data.target.name
      : ''
  );

  if (queryNode.isLoading) return <Loader />;
  if (!queryNode.data) return notFound();

  const node = queryNode.data;

  return (
    <DefaultTreeNodeBox
      node={node}
      options={{
        pagination: {
          enabled: true,
        },
        comments: {
          enabled: true,
          targetType: EntityTargetType.COURSE_NODE,
          targetAuthor: node.target.author,
        },
      }}
    />
  );
}


In DefaultTreeNodeBox I render various things, including Dialog. Here is that component:

import { ExamAttemptResultBox } from '@/app/(app)/account/activity/exam-attempt-card';
import { SolveExamDialog } from '@/app/(public)/exams/[id]/solve-exam/solve-exam-dialog';
import { SolveExamProvider } from '@/app/(public)/exams/[id]/solve-exam/solve-exam-provider';
import { RemoveExamAttemptDialog } from '@/app/(public)/exams/remove-exam-attempt-dialog';
import { CardActionsContainer } from '@/components/card-actions-container';
import { PlayIcon } from '@/components/icons/play-icon';
import { Button } from '@/components/ui/button';
import { TextGradient } from '@/components/ui/text-gradient';
import { Role } from '@/lib/enums';
import { getUserOrGuest } from '@/lib/utils';
import { Exam, ExamAttempt } from '@/types';
import { Eye } from 'lucide-react';

export const SolveExamBox = ({
  exam,
  examAttempt,
}: {
  exam: Exam;
  examAttempt?: ExamAttempt;
}) => {
  const user = getUserOrGuest();
  if (examAttempt) examAttempt.exam = exam;

  if (examAttempt?.result)
    return (
      <SolveExamProvider exam={exam} examAttempt={examAttempt}>
        <div className='relative w-full'>
          <CardActionsContainer>
            <div className='ml-auto flex items-center gap-2'>
              <SolveExamDialog
                trigger={
                  <Button variant='ghost' size='icon'>
                    <Eye />
                  </Button>
                }
              />
              <RemoveExamAttemptDialog examAttempt={examAttempt} />
            </div>
          </CardActionsContainer>
          <div className='flex w-full flex-col gap-2 rounded border bg-card p-3'>
            <div className='flex justify-between gap-3'>
              <TextGradient color='tricolor-gold' className='text-lg'>
                Wynik
              </TextGradient>
            </div>

            <ExamAttemptResultBox examAttempt={examAttempt} />
          </div>
        </div>
      </SolveExamProvider>
    );

  if (user && user.role !== Role.USER) return null;

  return (
    <div className='flex w-full flex-col gap-2 rounded border bg-card p-4'>
      <div className='flex items-center gap-2'>
        <PlayIcon className='mw-fill-gradient-gold h-10 w-10 shrink-0' />
        <h3 className='relative z-10 line-clamp-1 text-2xl font-bold duration-300'>
          <TextGradient color='black'>Rozwiąż online</TextGradient>
        </h3>
      </div>
      <SolveExamProvider exam={exam} examAttempt={examAttempt}>
        <SolveExamDialog />
      </SolveExamProvider>
    </div>
  );
};

Initially there is no result in examAttempt so user canclick to solve exam and it opens dialog. However, when user ends the exam, the node in main page is invalidated and because of that, the entire page rerenders and that closes the dialog. I would like to keep it open and only in background rerender things – is it possible? I tried to add the same keys to SolveExamProvider and SolveExam components in SolveExamBox but still context is completely rerendered when node is invalidated. Any ideas how to fix that?

Grammarly Chrome extension replaces my iframe dropdown options with __grammarly

I’m building a Wix Studio site. Inside it, I embed an iframe that loads a custom HTML page.

That page has an amCharts map and a dropdown () to switch datasets.

When the Grammarly Chrome extension is enabled, it messes with my .
As soon as I click the dropdown (or sometimes right on load), the options get replaced and I’m left with only:

<select id="countrySelect">
  <option value="__grammarly">__grammarly</option>
</select>

So all my original dropdown options disappear, and the UI breaks.

Is there a way to completely stop Grammarly from touching my iframe and dropdown?

Environment

  • Wix Studio (iframe embed)
  • amCharts (map + dropdown re-render)
  • Grammarly Chrome extension (causing the issue)

What is the Expo 54 way of creating a file in external storage?

I’ve recently upgraded to expo 54 (from 53) where expo-file-system/next got renamed to expo-file-system and expo-file-system to expo-file-system/legacy.

I’m currently trying to migrate from the deprecated (/legacy) module to the main one.
But I can’t seem to be able to create a file in external storage.

This is working code using the legacy module:

import * as FileSystem from "expo-file-system/legacy";
const SystemIO = FileSystem.StorageAccessFramework;

async function externalSave(fileName: string, content: string) {
  const documentUri = SystemIO.getUriForDirectoryInRoot("Documents");
  const permission = await SystemIO.requestDirectoryPermissionsAsync(documentUri);
  if (permission.granted) {
    const targetPath = permission.directoryUri;
    const createdPath = await SystemIO.createFileAsync(
      targetPath,
      fileName,
      "application/json",
    );
    await SystemIO.writeAsStringAsync(createdPath, content);
  } else {
    // error handling
  }
}

And this is the modern attempt

import { Directory, File } from "expo-file-system";

async function externalSave(fileName: string, content: string) {
  // uri => content://com.android.externalstorage.documents/tree/primary%3ADocuments/
  const directory = await Directory.pickDirectoryAsync("Documents");
  // exists => false
  const file= new File(directory.uri, `${fileName}.${jsonExtension}`);
  // throws here
  file.create({ overwrite: true });
  // Also tried write without create and got the same error cause
  file.write(content);
}

This is the error caused by file.create

Call to function ‘FileSystemFile.create’ has been rejected.
→ Caused by: A folder with the same name already exists in the file location

When the modern externalSave is called, the user is prompted to select a directory in the external storage (starting with /documents), a File object is created (whose exists property is false) and then the file.create call throws the above error.

What is the proper way of creating a file in external storage, and writing to it?

What is this React/JavaScript statement doing [duplicate]

I’m trying to understand a particular React/JavaScript statement and what is happening with this statement:

const ManyLinksCard = (props) => {
    const { classes, cardInfo: { configuration: { customConfiguration } } } = props;

    const backgroundStyle = customConfiguration?.urlImage
            ? { backgroundImage: `url(${customConfiguration.urlImage})` }
            : {};
    
    const source = customConfiguration.client || customConfiguration;
}

The statement in question is:

const backgroundStyle = customConfiguration?.urlImage

and the question mark ? within customConfiguration?.

What is that ? doing? Is customConfiguration an array so the statement would be customConfiguration1.urlImage, customConfiguration2.urlImage, etc.?

Button Hover Background Color

When i hover on these buttons, appear the text of the function of each button along with it’s background color, for some this color is a very dark gray, and the other it’s a white.

The client really wants to all of them to be white, but which property i change in CSS to change this bg color?

I tried changing the font-awesome, bootstrap and other basis code for the icons, but still nothing, even when changing the :hover property

Is there a basis integrated code the change this automatically?

Observation: We’re using Leaflet JS library, is by any chance this tooltip create on it?

[enter image description here](https://i.sstatic.net/51eVasiH.png)

    <li>
   <!--Toolbar-->
   <div id="editingToolbar" style="display: none;margin-top: 15px;">
      <!--<ul class="e-separator"  style="display: none">
         <li id="liSignOut" class="fa fa-sign-out fa-lg" title="Sair"></li>
         </ul>-->
      <ul class="e-separator">
         <li id="lnkSysConf" class="fa fa-cog fa-lg" title="Configurações"></li>
         <li id="liHelp"  class="fa fa-question-circle-o fa-lg" title="Ajuda"></li>
      </ul>
      <ul class="e-separator">
         <li id="liFullExtent" class="fa fa-arrows-alt fa-lg" title="Zoom a extensão"></li>
         <li id="liPreviousZoom" class="fa fa-arrow-left fa-lg" title="Zoom anterior"></li>
         <li id="liNextZoom" class="fa fa-arrow-right fa-lg" title="Zoom posterior"></li>
         <li id="liClearZHistory" class="fa fa-eraser fa-lg" title="Limpar histórico"></li>
      </ul>
      <ul class="e-separator">
         <li id="liThemes" title="Mapas temáticos">
            <i class="fa fa-map-o fa-lg"></i>
         </li>
         <!--<li id="liHelp" title="Ajuda">
            <i class="fa fa-question-circle-o fa-lg"></i>
            </li>-->
         <li id="liPrint" title="Imprimir">
            <i class="fa fa-print fa-lg"></i>
         </li>
      </ul>
      <ul class="e-separator">
         <li id="liLayers" class="mainnav-toggle slide" title="Mapas">
            <i class="fa fa-navicon fa-lg"></i>
         </li>
         <li id="liTables" title="Informações">
            <i class="fa fa-table fa-lg"></i>
         </li>
      </ul>
      <ul class="e-separator">
         <li id="liInfo">
            <i class="fa fa-info-circle fa-lg" title="Identificar"></i>
         </li>
         <li id="liMove">
            <i class="fa fa-hand-stop-o fa-lg" title="Mover"></i>
         </li>
         <li id="liSelect">
            <i class="fa fa-mouse-pointer fa-lg" title="Selecionar"></i>
         </li>
      </ul>
   </div>
</li>

Leaflet CSS Code:

/* Variables and Mixins */
/* Generic L.Toolbar */
.leaflet-toolbar-0 {
  list-style: none;
  padding-left: 0;
  border: 2px solid rgba(0, 0, 0, 0.2);
  border-bottom-left-radius: 4px;
  border-bottom-right-radius: 4px;
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
}
.leaflet-toolbar-0 > li {
  position: relative;
}
.leaflet-toolbar-0 > li > .leaflet-toolbar-icon {
  display: block;
  width: 30px;
  height: 30px;
  line-height: 30px;
  margin-right: 0;
  padding-right: 0;
  border-right: 0;
  text-align: center;
  text-decoration: none;
  background-color: #ffffff;
}
.leaflet-toolbar-0 > li > .leaflet-toolbar-icon:hover {
  background-color: #f4f4f4;
}
.leaflet-toolbar-0 .leaflet-toolbar-1 {
  display: none;
  list-style: none;
}
.leaflet-toolbar-tip-container {
  margin: 0 auto;
  margin-top: -16px;
  height: 16px;
  position: relative;
  overflow: hidden;
}
.leaflet-toolbar-tip {
  width: 16px;
  height: 16px;
  margin: -8px auto 0;
  background-color: #ffffff;
  border: 2px solid rgba(0, 0, 0, 0.2);
  border-bottom-left-radius: 4px;
  border-bottom-right-radius: 4px;
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
  background-clip: content-box;
  -webkit-transform: rotate(45deg);
      -ms-transform: rotate(45deg);
          transform: rotate(45deg);
}
/* L.Toolbar.Control */
.leaflet-control-toolbar {
  /* Secondary Toolbar */
}
.leaflet-control-toolbar > li > .leaflet-toolbar-icon {
  border-bottom: 1px solid #ccc;
}
.leaflet-control-toolbar > li:first-child > .leaflet-toolbar-icon {
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
}
.leaflet-control-toolbar > li:last-child > .leaflet-toolbar-icon {
  border-bottom-left-radius: 4px;
  border-bottom-right-radius: 4px;
  border-bottom-width: 0;
}
.leaflet-control-toolbar .leaflet-toolbar-1 {
  margin: 0;
  padding: 0;
  position: absolute;
  left: 30px;
  /* leaflet-draw-toolbar.left + leaflet-draw-toolbar.width */
  top: 0;
  white-space: nowrap;
  height: 30px;
}
.leaflet-control-toolbar .leaflet-toolbar-1 > li {
  display: inline-block;
}
.leaflet-control-toolbar .leaflet-toolbar-1 > li > .leaflet-toolbar-icon {
  display: block;
  background-color: #919187;
  border-left: 1px solid #aaa;
  color: #fff;
  font: 11px/19px "Helvetica Neue", Arial, Helvetica, sans-serif;
  line-height: 30px;
  text-decoration: none;
  padding-left: 10px;
  padding-right: 10px;
  height: 30px;
}
.leaflet-control-toolbar .leaflet-toolbar-1 > li > .leaflet-toolbar-icon:hover {
  background-color: #a0a098;
}
.leaflet-control-toolbar .leaflet-toolbar-1 > li:last-child > .leaflet-toolbar-icon {
  border-top-right-radius: 4px;
  border-bottom-right-radius: 4px;
}
/* L.Toolbar.Popup */
.leaflet-popup-toolbar {
  position: relative;
  box-sizing: content-box;
}
.leaflet-popup-toolbar > li {
  float: left;
}
.leaflet-popup-toolbar > li > .leaflet-toolbar-icon {
  border-right: 1px solid #ccc;
}
.leaflet-popup-toolbar > li:first-child > .leaflet-toolbar-icon {
  border-top-left-radius: 4px;
  border-bottom-left-radius: 4px;
}
.leaflet-popup-toolbar > li:last-child > .leaflet-toolbar-icon {
  border-top-right-radius: 4px;
  border-bottom-right-radius: 4px;
  border-bottom-width: 0;
  border-right: none;
}
.leaflet-popup-toolbar .leaflet-toolbar-1 {
  position: absolute;
  top: 30px;
  left: 0;
  padding-left: 0;
}
.leaflet-popup-toolbar .leaflet-toolbar-1 > li > .leaflet-toolbar-icon {
  position: relative;
  float: left;
  width: 30px;
  height: 30px;

Yarn/npm scripts with backtick markers #J_S/#J_E work on macOS but fail on Windows (“#J_Scd is not recognized”) — how to make this cross‑platform?

I’m trying to run yarn install (and other scripts) for a project that’s partially auto‑generated by an internal platform. The platform injects special markers around script commands in package.json:

  • SCRIPT_START_MARKER = #J_S
  • SCRIPT_END_MARKER = #J_E

Example snippets from package.json:


`{
  "scripts": {
    "postinstall": "`#J_S`cd node_modules/@wingify/jslib-platform && grunt init-app || true`#J_E`&& cd - && node -e "try { require('fs').symlinkSync(require('path').resolve('node_modules/@bower_components'), 'bower_components', 'junction') } catch (e) { }"",
    "docs": "`#J_S`grunt typedoc`#J_E`",
    "ts": "`#J_S`grunt ts`#J_E`",
    "merge-configs": "`#J_S`cd node_modules/@wingify/jslib-platform && grunt merge-configs`#J_E`"
  }
}`

On macOS, running yarn install succeeds. On Windows (PowerShell/CMD), it fails inside dependencies that also contain these markers (e.g., @wingify/campaign-runner).

Error on Windows:

yarn install v1.22.22
[5/5] Building fresh packages...
error D:projectnode_modules@wingifycampaign-runner: Command failed.
Exit code: 1
Command: `#J_S`cd node_modules/@wingify/jslib-platform && grunt init-app || true`#J_E`
Output:
'`#J_S`cd' is not recognized as an internal or external command,

Environment

  • Windows: PowerShell 5.1 (also tried CMD), Yarn 1.22.22
  • macOS: Works with the same repo without any extra tool
  • package.json is auto-generated; markers are inserted by a script that defines:
    `const SCRIPT_START_MARKER = '`#J_S`';
    const SCRIPT_END_MARKER = '`#J_E`';`
    
  • I cannot easily remove the markers from dependencies; they’re reinstalled by yarn.

What I’ve tried

  • Running yarn install normally on Windows → fails as above.
  • Setting npm’s script shell to Git Bash so npm/yarn scripts run under bash:
    npm config set script-shell "C:Program FilesGitbinbash.exe"
    npm config get script-shell
    

    This helps in some setups, but I still hit failures when a dependency’s postinstall runs with these markers.

  • Using WSL (Ubuntu) and running yarn install from /mnt/d/… → works, but I’d prefer a Windows-native approach.
  • Searching for a yarn/npm feature to ignore/strip these markers → couldn’t find any.

Questions

  1. Is there any official way to make npm/yarn treat those backtick‑wrapped markers as harmless on Windows (similar to how they effectively don’t break in bash on macOS)?
  2. Is configuring npm’s script‑shell to Git Bash the recommended/best practice for this case, or is there a better cross‑platform approach?
  3. If Git Bash is the solution, how can I ensure all dependency scripts (including postinstall in node_modules) reliably execute under bash on Windows?
  4. If the only fix is “remove the markers,” what’s a practical strategy when dependencies’ package.json also contain them and get reinstalled by yarn? Is there a preinstall hook, patch-package, or other common workaround?

Goal
I want yarn install and subsequent yarn scripts (e.g., build) to work on Windows without manually editing node_modules each time or maintaining a separate Windows-only branch.

Tags

  • node.js
  • npm
  • yarnpkg
  • windows
  • bash
  • cross-platform
  • postinstall

If you think I’m missing a standard pattern for cross‑platform npm/yarn scripts here, please point me to it. Thanks!