React: useEffect(() => f(a,b), [a]) without disabling react-hooks/exhaustive-deps

I run into this more and more frequently in various forms: (simplified example)

const searchParameters = useState();
const sorting = useState();

// update searchParameters if the user enters someting
// update sorting if the user clicks on a column in the table

// PROBLEM: I only want to call loadEntries() if sorting changes, but I need searchParameters for it too!
useEffect(
    () => loadEntries(sorting, searchParameters),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [sorting]
);
  1. Is there a way to deal with this situation without having to disable react-hooks/exhaustive-deps?
  2. (less important) is there a way to disable react-hooks/exhaustive-deps for only a specific dependency? (searchParameters in this case)

Upload multiple file objects to API as 2d array of files

I am trying to build an API using django which support file uploads as an array of array. An example of what I am trying to achieve is,

[
    {
        "string": "Some string",
        "files": [
            "<uploaded file object 1>",
            "<uploaded file object 2>"
        ]
    },
    {
        "string": "Some string",
        "files": [
            "<uploaded file object 3>"
        ]
    },
    {
        "string": "Some string",
        "files": []
    }
]

I dont want to use base64 encoded files for this since the files can be sometimes large, so I dont want to increase the overheads using base64.

**how can I achieve this API call in the most efficient way and what would be most appropriate structure to call this API and help with some javascript frontend code do it.
**

I tried to move this to a FormData where I can acheive the a above upload like below but I am not able to call the API from the frontend, it doesnt work probably because I am writing wrong code.

strings: ["Some string", "Some string", "Some string"]
files: [["<uploaded file object 1>", "<uploaded file object 2>"], ["<uploaded file object 3>"]]

For my backend I am using django(rest framework), here is the relevant part of the serializer if required

class StringSerializer(serializers.Serializer):
    serializers = serializers.ListField(child=serializers.CharField())
    files = serializers.ListField(
        child=serializers.ListField(child=serializers.FileField()),
        write_only=True,
        required=False,
        allow_empty=True,
        default=[],
    )

How to reference an module’s type from application which uses this module?

I’m currently developing on monorepo, implementing internal module which has additional features base on nestjs-prisma

project structure is as following

     - apps
    |    |_serviceA
    |        |_node_modules
    |             |_myPrismaModule (install built version of internal)
    |        |_src
    |           |_generated
    |               |_client
    |                   |_default.js (where my required type lies in)
    |
    |- internal
    |     |_myPrismaModule

Since I’m using Prisma, PrismaClient & Prisma type will defer across all service , cuz its type is defined by how developer designs the prisma schema.

The problem I’m facing is that I can’t reference the type properly after interna module build.. (actually I can’t guarantee whether this problem is reference problem or not..)

My implementation is the following code, previously it worked well but after changes it started not to reference well.

//previous
import {Prisma, PrismaClient} from "@prisma/client"

@Injectable()
export class PrismaService
  extends PrismaClient<Prisma.PrismaClientOptions, 'query' | 'info' | 'warn' | 'error'>
  implements OnModuleInit{...}


------

// current


async function getPrismaClientType() {
  const {Prisma, PrismaClient} = await import(
    "~~absolutePath~~/src/apps/serviceA/src/generated/client/default.js'"
  );
  return PrismaClient as PrismaClient<
   Prisma.PrismaClientOptions,
    'query' | 'info' | 'warn' | 'error'
  >;
}
@Injectable()
export class PrismaService
  extends (await getPrismaClientType())
  implements OnModuleInit{...}

It throwed Cannot find namespace 'Prisma' but I just to proceed I just comment @ts-ignore in this case.

And In service workspace, whenever I try to access type like below, compiler just thinks type as “any”.. any advise in this??

  prismaService.banner.findMany // banner is any

I’m not getting the full data sent to the server via axios

I want to send a student data to the backend server via axios.post, but im not getting the full object in my server route

Here’s the ‘student’ object:

{
    "sisn": "202401",
    "level": "K1",
    "registration_date": "2024-11-01T05:56:27.705Z",
    "custodian": "Mother",
    "personal": {
        "firstname": "Jack",
        "middlename": "Sparrow",
        "lastname": "James",
        "lrn": "333333331111",
        "gender": "Male",
        "email": "[email protected]",
        "birthdate": "2020-11-01",
        "height": "4'8"",
        "blood_type": "B",
        "food_allergen": "None",
        "cell": "0915-881-5474",
        "weight": "43",
        "fortitude": "Normal",
        "siblings": 1,
        "photo_filename": "daniella.jpg",
        "photo": {}
    },
    "parents": {
        "mother": {
            "firstname": "Sssss",
            "middlename": "Sdfsdfsd",
            "lastname": "Assss",
            "address_line1": "Lot 1, Blk 2 Guyabano St., Brgy 178",
            "address_line2": "Camarin Caloocan City",
            "email": "[email protected]",
            "occupation": "housewife",
            "cell": "0945-555-5555",
            "marital_status": "Married",
            "photo_filename": "daniella.jpg",
            "photo": {}
        }
    }
}

The ‘save’ button in my app that has an OnClick handler

const AddStudent = async (e) => {
await axios.post('http://localhost:4000/api/students/add', student,
{ headers: { "Content-Type": "multipart/form-data",}
}).then(result => {
SetIsStudentCreationShown(false)
SetIsStudentSummaryShown(true)
}).catch(error => {
console.log('something went wrong')
})
}

The route handler (some i deleted intentionally, im using multer )

    router.post('/add', (req,res,next) => {
      upload(req, res, async (err) => { 
       console.log(req.body)

      more code...
     }
   }

the console.log(req.body) shows some but now all the data in my object

    [Object: null prototype] {
      sisn: '202401',
      level: 'K1',
      registration_date: '2024-11-01T05:18:53.625Z',
      custodian: 'Mother',
      personal: [Object: null prototype] {
      firstname: 'Rowel',
      middlename: 'Gomez',
      lastname: 'Sss',
      lrn: '333333331111',
      gender: 'Male',
      email: '[email protected]',
      birthdate: '2020-11-01',
      height: `4'8"`,
      blood_type: 'B',
      food_allergen: 'None',
      cell: '0915-881-5474',
      weight: '43',
      fortitude: 'Normal',
      siblings: '1',
      photo_filename: 'daniella.jpg'
    }
  }

For some reason there is no parent field in the object. The weird thing is if I change the entry order of the field, parents first and then personal, the personal field in the object is nowhere to be found.

I need to have access to the whole student object inside my student route.
What should I do.

Here’s the Onchange handler of my form that set the student State:

const HandleChangeFormInputs =(e) => {
e.stopPropagation();
let inputName = e.target.name; 
if (inputName.search('student')!==-1) {
    if (inputName.search('height')!==-1) { //5'9" or 5'10"
        var heightState, height;
        //allows empty value
        if (!e.target.value) heightState = e.target.value; 
        //removes all the formating
        height = e.target.value.replace(/[^d]/g,''); 
        //allows values that has only feet number, no inches
        if (height.length === 1) heightState = height; 
        //accepts feet number and single digit inches  
        if (height.length === 2) heightState = `${height.slice(0,1)}'${height.slice(1,2)}"`;
        if (height.length === 3) heightState = `${height.slice(0,1)}'${height.slice(1,3)}"`;
        SetStudent({...student,personal:{...student.personal,height:heightState}}); 
    }
    // works for all input with mobile numbers 0915 881-5767    
    else if (inputName.search('cell') !==-1) { 
        let unmaskednumber, maskednumber; 
        //not really masked, since value is empty, only just for the code below
        if (!e.target.value) maskednumber = e.target.value; 
        else {
            //removes all the formating 
            unmaskednumber = e.target.value.replace(/[^d]/g,''); 
            if (unmaskednumber.length <5) maskednumber = `${unmaskednumber.slice(0,e.target.value.length)}`; 
            else if (unmaskednumber.length < 8) maskednumber = `${unmaskednumber.slice(0,4)}-${unmaskednumber.slice(4,7)}`;
            else if (unmaskednumber.length < 12) maskednumber = `${unmaskednumber.slice(0,4)}-${unmaskednumber.slice(4,7)}-${unmaskednumber.slice(7,11)}`;
        }
        SetStudent({...student,personal:{...student.personal,cell:maskednumber}});
    }
    else {
        var name = inputName.slice(8);
        SetStudent({...student,personal:{...student.personal,[name]:e.target.value}});
    }
}
else if (inputName.search('mother')!==-1) {
    if (inputName.search('cell') !==-1) { 
        let unmaskednumber, maskednumber; 
        //not really masked, since value is empty, only just for the code below
        if (!e.target.value) maskednumber = e.target.value; 
        else {
            unmaskednumber = e.target.value.replace(/[^d]/g,''); //removes all the formating
            if (unmaskednumber.length <5) maskednumber = `${unmaskednumber.slice(0,e.target.value.length)}`; 
            else if (unmaskednumber.length < 8) maskednumber = `${unmaskednumber.slice(0,4)}-${unmaskednumber.slice(4,7)}`;
            else if (unmaskednumber.length < 12) maskednumber = `${unmaskednumber.slice(0,4)}-${unmaskednumber.slice(4,7)}-${unmaskednumber.slice(7,11)}`;
        }
        SetStudent({...student,parents:{...student.parents,mother:{...student.parents.mother,cell:maskednumber}}});
    }
    else {
        var name = inputName.slice(7);
        SetStudent({...student,parents:{...student.parents,mother:{...student.parents.mother,[name]:e.target.value}}});
    }

}
else if (inputName.search('father')!==-1) {

}
else {
SetStudent({...student,[inputName]:e.target.value});
}

} //end of HandleChangeFormInputs

Infinite Vite HMR updates to tailwind.css file – Ionic project

I have an Ionic+React+Tailwind+React project. I am relatively new to frontend development, especially Vite. When I run my project, I see there are “HMR updates” in a loop coming from the tailwind.css file. Even though I am not making any changes to the tailwind.css file.

Attaching an image of the “updates” log.

strong text

The package.json –

{
  "name": "flow-fa",
  "private": true,
  "version": "0.0.1",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "test.e2e": "cypress run",
    "test.unit": "vitest",
    "lint": "eslint",
    "build:css": "postcss tailwind.css -o src/theme/tailwind.css",
    "watch:css": "postcss tailwind.css -o src/theme/tailwind.css -w",
    "start:dev": "npm run watch:css & ionic capacitor run android -l --external"
  },
  "dependencies": {
    "@capacitor/android": "6.1.2",
    "@capacitor/app": "6.0.1",
    "@capacitor/core": "6.1.2",
    "@capacitor/haptics": "6.0.1",
    "@capacitor/keyboard": "6.0.2",
    "@capacitor/status-bar": "6.0.1",
    "@ionic/react": "^8.0.0",
    "@ionic/react-router": "^8.0.0",
    "@types/react-router": "^5.1.20",
    "@types/react-router-dom": "^5.3.3",
    "ionicons": "^7.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-icons": "^5.3.0",
    "react-router": "^5.3.4",
    "react-router-dom": "^5.3.4"
  },
  "devDependencies": {
    "@capacitor/cli": "6.1.2",
    "@testing-library/dom": ">=7.21.4",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^14.0.0",
    "@testing-library/user-event": "^14.4.3",
    "@types/react": "^18.0.27",
    "@types/react-dom": "^18.0.10",
    "@vitejs/plugin-legacy": "^5.0.0",
    "@vitejs/plugin-react": "^4.0.1",
    "autoprefixer": "^10.4.20",
    "cypress": "^13.5.0",
    "eslint": "^8.35.0",
    "eslint-plugin-react": "^7.32.2",
    "jsdom": "^22.1.0",
    "postcss-cli": "^11.0.0",
    "tailwindcss": "^3.4.14",
    "terser": "^5.4.0",
    "typescript": "^5.1.6",
    "vite": "~5.2.0",
    "vitest": "^0.34.6"
  },
  "description": "An Ionic project"
}

The vite.config.ts

/// <reference types="vitest" />

import legacy from '@vitejs/plugin-legacy'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    legacy()
  ],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/setupTests.ts',
  }
})

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{js,jsx,jsx,ts,tsx,css}'],
  darkMode: 'media',
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

Tailwind.css

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

Please let me know if I shud be adding any other details. I need to get this fixed because I guess due to this, the app behaves weird too. All of a sudden RAW HTML will get rendered and then it’ll go back to the styled one.

Flask session variable not persisting between Ajax requests

In my Flask app, I’m trying to display the progress of a backend task to the client. The client sends an initial request which begins the task. The task contains a loop, and as it iterates, it updates a session variable which contains the completion percentage. The client then regularly sends additional requests to retrieve the value of the session variable so it can be displayed.

The problem is that whenever the session variable is retrieved, it only contains its initial value of 0%. If I replace the session variable with a global variable, it works fine, but the app needs to support multiple simultaneous users, so I can’t use a global variable. My understanding is that session variables are meant for this exact purpose, to persist between requests within the same session, but I must be missing something, because it’s not doing so here.

Here’s a simplified version of my Flask code:

@app.route("/task")
def task():
    session["load_percent"] = 0
    percent = 0
    
    for i in range(10000000):
        #actual task performed here
            
        old_percent = percent
        percent = round(i / 10000000 * 100)
        if old_percent != percent:
            session["load_percent"] = percent
            print("Actual percentage: " + str(session["load_percent"]))
            #this prints the correct value of the session variable

    return "{}"

@app.route("/get_load_percent")
def get_load_percent():
    try:
        print("Retrieved percentage: " + str(session["load_percent"]))
        #this always prints 0
        return json.dumps({"load_percent": session["load_percent"]})
    except (KeyError):
        return "{}"

And my frontend JS:

function startTask() {
    $.ajax({
        type: "GET",
        url: $('body').data('task'),
        contentType: "application/json",
        dataType: 'json',
        success: function(response) {
            //respond to task completion here
        }
    });

    let percentageHTML = "";
    let percentageInterval = setInterval(function() {
        $.ajax({
            type: "GET",
            url: $('body').data('getloadpercent'),
            contentType: "application/json",
            dataType: 'json',
            success: function(response) {
                percentageHTML = response["load_percent"];
                console.log(percentageHTML);
                //always prints 0
            }
        })
    }, 100);
}

I’ve checked similar questions, and their solutions don’t work. The session variable does not exceed the size limitations, and the key is being set every time rather than having its value changed in place, so it doesn’t need to be marked as modified.

IntersectionObserver offscreen bounds

Is it possible to create an Intersection Observer that only detects an intersection for vertical changes?

I tried setting a large enough horizontal rootMargin for my purposes:

{
  threshold: 1,
  rootMargin: '-57px 300% 0px 300%',
}

But the resulting boundingClientRect still seems to only intersect with the viewport:

IntersectionObserverEntry { ​
  boundingClientRect: DOMRect { 
    bottom: 441.1499938964844,
​​    height: 175.91665649414062,
​​    left: 250,
​​    right: 2167.066650390625,
​​    top: 265.23333740234375,
​​    width: 1917.066650390625,
​​    x: 250,
​​    y: 265.23333740234375
  },
​  intersectionRatio: 0.9879677284740576,
​
  intersectionRect: { 
    ​​bottom: 441.1499938964844,
    ​​height: 175.91665649414062,
    ​​left: 250,
    ​​right: 2144,
    ​​top: 265.23333740234375,
    ​width: 1894,
    ​​x: 250,
    ​​y: 265.23333740234375
​​  },
​
  isIntersecting: false,
​
  rootBounds: {
​​    bottom: 1191.4833984375,
​​    height: 1134.4833984375,
​​    left: 0,
​​    right: 8576,
​​    top: 57,
​​    width: 8576,
​​    x: 0,
​​    y: 57
  }

Why is entry.intersectionRect.right limited to 2144 when rootBounds.right is 8576?

How can I prevent a webpage from displaying a bookmarklet’s javascript text in the URL bar after running it?

I have a bookmarklet written in the form javascript:(function(){ [do some stuff to current page]; })(); that I run from Firefox. It acts on the webpage as intended, but running it replaces the website’s URL in the URL bar with the text of the bookmarklet.

Searching around for solutions it seemed as though pushState() or replaceState() should do the trick(?), but I can’t figure out how to implement them correctly.

I’ve tried adding history.replaceState({},'',window.location.protocol + '//' + window.location.hostname + window.location.pathname); as the last line inside the bookmarklet’s function(){}, but still the javascript text shows up in the URL bar instead of the URL specified by replaceState() after running. Duplicating or refreshing the window afterwards confirms that search queries in the original URL have been removed, so replaceState() is apparently working, but the text of the URL bar still isn’t being updated to match it.

JavaScript Web API. Failed to execute ‘decodeAudioData’ on ‘BaseAudioContext’: Unable to decode audio data

I have a website that sends dynamically generated text to the server and the server returns the voiceover of this text to the site. Audio is transmitted in chunks, each 0.33 seconds long. The initial size of the transmitted voice file is unknown, because it is generated on a third-party API at the time of the request. Chunks come in the ReadableStream interface, each chunk is in uint8array format. The global task is to make the voice acting sound as the chunk is uploaded to the site.
I start reading the Readable Stream through reader and voicing these chunks as they are uploaded to the site. But I ran into a problem. The first chunk voices its part of the text (0.33 seconds), but the next ones give an error: Uncaught (in promise) EncodingError: Failed to execute ‘decodeAudioData’ on ‘BaseAudioContext’: Unable to decode audio data.
This is my code:

const response = await fetch(voiceAPI, requestOptions);
        
const reader = await response.body.getReader();

const readChunk = () => {
    reader.read().then(({value, done}) => {

        if (value) {
            let audioContext = new AudioContext(); 
            let audioSourceNode = audioContext.createBufferSource();
            audioSourceNode.connect(audioContext.destination);
            
            new Promise((resolve) => {
                let decodedBuffer = audioContext.decodeAudioData(value.buffer);
                resolve(decodedBuffer);
                })
                .then((decodedBuffer) => {
                    audioSourceNode.buffer = decodedBuffer;
                    audioSourceNode.start(0);
                    audioSourceNode.addEventListener('ended', () => {
                        audioSourceNode.buffer = null;
                        readChunk();
                })
            })
        } else {
            console.log('Stream is completed');
        }
    })
};
readChunk(); 

The problem is somehow related to decodeAudioData method, error occurs on the line with it’s call and only on the “second” call of decodeAudioData. The WebAPI documentation says that “This method only works on complete file data, not fragments of audio file data.” So I tried to make every time a new audioContext with new audioSourceNode, but that is not working.
I also tried to make global audioContext and not creating new one every iteration of readChunk();

let audioContext = new AudioContext(); 
let audioSourceNode;
const readChunk = () => {
    reader.read().then(({value, done}) => {

        if (value) {
            audioSourceNode = audioContext.createBufferSource(); 
            audioSourceNode.connect(audioContext.destination);

            // also tried "let audioSourceNode = audioContext.createBufferSource();"
            
            new Promise((resolve) => { /// decodeAudioData code }

I also tried to make one big array of all chunks and then voice them, from one call of decodeAudioData, and it works. But about 8 seconds pass between clicking on the voice-over button and the voice-over itself with this approach if text contains 1000+ symbols. So it’s not an option.

I will be glad of any help or idea. I’m definitely missing something, but I can’t figure out what.

Determine if popstate event handler was trigger programmatically?

I’m calling history.back() and history.forward(), but I want to have a popstate handler just for events triggered by the browser. In other words, I’d like to ignore popstates triggered programmatically. Is this possible?

The only possible solutions I can think of involve keeping track of all the history states in my webapp, then every time I call history.back(), I’d know what state would be in event.state, so I know what to ignore. However, I can think of edge cases where this might not work: if a lot of navigations happen before popstate runs, which is unlikely but possible.

Is there a better way?

Context:

Currently, my webapp updates app state on popstate, but this is noticeably laggy on slower devices. E.g. user clicks an in-app “back” button -> trigger history.back() -> popstate handler -> update app state -> rerender. Instead, I want to change it to: user clicks an in-app “back” button -> update app state -> rerender -> trigger history.back(). I still need the popstate handler for browser backs. If the user navigates multiple times very quickly, then the popstate handler would behave weirdly, which I want to want to avoid.

Speech Synthesis Api Language on phone (andriod) doesn’t support Spanish?

Hello I have a snipped of code, I generate some speech based on my element data-audio. Everything works fine, except on mobile phone where the language is “English” even though I set it in Spanish.
So I’ve done some research and found that maybe the language is not supported or the English language is set by default.

I would like to know if you have a solution for me please? I share with you my javascript code.
As you can see I put the default language in Spanish, but it still doesn’t work, so I might need your help. Thank you in advance!

const audioBlocks = document.querySelectorAll(".audio-trigger-button");

// Variable to hold the currently playing utterance
let currentUtterance = null;
let isPlaying = false;

// Function to hide all play buttons
function hideAllPlayButtons() {
  audioBlocks.forEach((audioBlock) => {
    const listenIcon = audioBlock.querySelector(".audio-icon-listen");
    const pauseIcon = audioBlock.querySelector(".audio-icon-pause");
    if (listenIcon) {
      listenIcon.classList.remove("hidden"); // Show listen icon
    }
    if (pauseIcon) {
      pauseIcon.classList.add("hidden"); // Hide pause icon
    }
  });
}

// Function to check available voices
function checkAvailableVoices() {
  const voices = window.speechSynthesis.getVoices();
  voices.forEach((voice) => {
    console.log(`${voice.name} - ${voice.lang}`);
  });
}

// Call this function when the voices are loaded
speechSynthesis.onvoiceschanged = checkAvailableVoices;

// Add click event listener to each audio block
audioBlocks.forEach((audioBlock) => {
  audioBlock.addEventListener("click", function () {
    // Get the audio text from the data attribute
    const audioText = this.getAttribute("data-audio");

    // Get the icons for play and pause
    const listenIcon = this.querySelector(".audio-icon-listen");
    const pauseIcon = this.querySelector(".audio-icon-pause");

    // Hide all play buttons
    hideAllPlayButtons();

    // Check if the clicked audio block is already playing
    if (currentUtterance && this === currentUtterance.audioBlock) {
      // If the same button is clicked again, toggle between pause and play
      if (isPlaying) {
        window.speechSynthesis.pause();
        isPlaying = false;
        listenIcon.classList.remove("hidden"); // Show listen icon
        pauseIcon.classList.add("hidden"); // Hide pause icon
      } else {
        window.speechSynthesis.resume(); // Resume speaking
        isPlaying = true;
        listenIcon.classList.add("hidden"); // Hide listen icon
        pauseIcon.classList.remove("hidden"); // Show pause icon
      }
      return; // Exit the function to avoid creating a new utterance
    }

    // If there's an active utterance, cancel it
    if (currentUtterance) {
      window.speechSynthesis.cancel();
    }

    // Check for available voices
    const voices = window.speechSynthesis.getVoices();
    const spanishVoice = voices.find((voice) => voice.lang === "es-ES");

    // Create a new SpeechSynthesisUtterance instance
    currentUtterance = new SpeechSynthesisUtterance(audioText);
    if (spanishVoice) {
      currentUtterance.voice = spanishVoice; // Use the Spanish voice if available
    }
    currentUtterance.lang = "es-ES"; // Set the language for good measure
    currentUtterance.audioBlock = this; // Store reference to the audio block

    // Speak the text
    window.speechSynthesis.speak(currentUtterance);
    isPlaying = true;
    listenIcon.classList.add("hidden"); // Hide listen icon
    pauseIcon.classList.remove("hidden"); // Show pause icon

    // Add event listener for when speech ends
    currentUtterance.onend = function () {
      isPlaying = false;
      listenIcon.classList.remove("hidden"); // Show listen icon
      pauseIcon.classList.add("hidden"); // Hide pause icon
      currentUtterance = null; // Reset currentUtterance
    };

    // Add event listener for when speech is paused
    currentUtterance.onpause = function () {
      isPlaying = false;
      listenIcon.classList.remove("hidden"); // Show listen icon
      pauseIcon.classList.add("hidden"); // Hide pause icon
    };

    // Add event listener for when speech is resumed
    currentUtterance.onresume = function () {
      isPlaying = true;
      listenIcon.classList.add("hidden"); // Hide listen icon
      pauseIcon.classList.remove("hidden"); // Show pause icon
    };
  });
});

// Stop any audio when the page is reloaded
window.addEventListener("beforeunload", () => {
  if (currentUtterance) {
    window.speechSynthesis.cancel();
  }
});

fabricjs: how to use loadSVGFromURL with react useEffect?

I tried to render a SVG onto my canvas using useEffect, like this:

// store canvas reference
const [canvas, setCanvas] = useState<fabric.Canvas>();
    useEffect(() => {
        const c = new fabric.Canvas("canvas", {
          height: 700,
          width: 700,
          backgroundColor: "white",
        });
        setCanvas(c);
        c.renderAll();
        return () => {
          c.dispose();
        };
      }, []);

    useEffect(() => {
      fabric.loadSVGFromURL('/example.svg', (objects, options) => {
        const svg = fabric.util.groupSVGElements(objects, options);
        // Add the SVG to the canvas and render
        canvas?.add(svg);
        canvas?.renderAll();
      })
    // wait for canvas to mount first
    }, [canvas]);

However there is nothing being rendered on the canvas, I found no similar projects online and don’t know the correct practice in loading SVG with react. What am I doing wrong?

How to extend the background to the entire webpage?

Blockquote

I set .matrix-background to position: fixed and adjusted width, height, and top properties to make it cover the full page.
I used document.body.scrollHeight in JavaScript to calculate the page height, hoping the animation would cover the entire page.

I want the matrix background on the whole webpage even if i scroll down till the end but i'm only getting the background till the first container 

here is my index.html Matrix background

 .matrix-background {
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            pointer-events: none;
            z-index: -1;
            overflow: hidden;
        }

and this is my script for the background

const characters =
“ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789”;
const matrixBackground = document.querySelector(‘.matrix-background’);

                function createMatrix() {
                    const span = document.createElement('span');
                    span.style.position = 'absolute';
                    span.style.color = 'green';
                    span.style.fontSize = '20px';
                    span.style.whiteSpace = 'nowrap';
                    span.style.opacity = Math.random();
                    span.textContent = characters.charAt(Math.floor(Math.random() * 
         characters.length));
            
                     const x = Math.random() * window.innerWidth;
                     span.style.left = `${x}px`;
                     span.style.top = `-${Math.random() * 100}px`;  // Start just 
            above the viewport
                     matrixBackground.appendChild(span);
            
                      // The span falls down to cover the visible viewport and beyond 
       to mimic continuous effect
                       const fallDuration = Math.random() * 3 + 2;
                       span.animate([{ transform: 'translateY(0)' }, { transform: 
         `translateY(${window.innerHeight * 2}px)` }], {
                           duration: fallDuration * 1000,
                          easing: 'linear',
                           iterations: 1,
                          fill: 'forwards',
                      });
            
                      setTimeout(() => {
                           span.remove();
                       }, fallDuration * 1000);
                   }

                 setInterval(createMatrix, 100);
                </script>

Blockquote

Chrome Extension: SpeechSynthesis does not output sound on the first call

I am trying to output sound using SpeechSynthesis. I pre-defined speechSynthesis and SpeechSynthesisUtterance:

TTS = window.speechSynthesis;
utter = new SpeechSynthesisUtterance();
...

let voices = speechSynthesis.getVoices();
this.utter.voice = voices[3];
this.utter.name = voices[3].name;
this.utter.lang = voices[3].lang;
this.utter.voiceURI = voices[3].voiceURI;

When I try to output sound like:

this.utter.text = "say something";
this.TTS.speak(this.utter);

There is no output the first time I call TTS.speak, but this.TTS.speaking becomes true. It happens after it doesn’t play any speech for several minutes.

If I play it a second time, it works:

this.TTS.speak(this.utter);
this.TTS.cancel();
this.TTS.speak(this.utter);

Also, if I make a copy of this.utter and play that copy, it works as well:

let utterance = new SpeechSynthesisUtterance(this.utter.text);
utterance.voice = this.utter.voice;
utterance.name = this.utter.name;
utterance.lang = this.utter.lang;
utterance.voiceURI = this.utter.voiceURI;
this.TTS.speak(utterance);

I found speechSynthesis.speak() without user activation is no longer allowed since M71, around December 2018. However, in this case, each TTS.speak call is triggered by a play button.