useSelector inside of a hook doesn’t update correctly

I am trying to create a hook that extracts some redux related actions that i will reuse in many components. the hook includes dispatching and reading selectors.

The problem is that that the selector doesn’t update correctly, the redux dev tool shows an array with a single element but when logging the selector the same array is empty.

hook code :

function useManager() {
  const [isManaged, setManaged] = useState(false)
  const dispatch = useAppDispatch() // just a re-export of redux's useDispatch
  let stackArray = useAppSelector(stack) // stack is a defined selector (state => state.stack)
  const checkArrayLength = () => {
    return stack.length > 0;
  }

  const setManagedState = useCallback(
    () => {
      if(checkArrayLength()){
        console.log(stack.length) <== will always log 0
        setManaged(true)
      }
    },
    [],
  )

  return {
    setManagedState,
    isManaged,
  }
}

in a different component i call this hook :

function Component(props) {

    const { setManagedState, isManaged } = useManager();
    //...
    / * I use useSelector inside this component to update the store stack */
    //...
    
     setManagedState() // will log 0, even if i push to the stack and i can see the changes in the redux tools. 

}

is there something i should check ?, i tried to check the equality Function of useSelector (second arg), but i didn’t understand the issue from there.

I am relatively new to functional components and hooks, so i am probably missing something (maybe related to rendering cycles needed for the useSelector hook to update).

localStorage Chrome Extension

Im currently working on my antifishing chrome extension, but everytime that Im trying to add url to whitelist it leads extension to error. here is the code of my background.js:

const API_KEY = "api_key";

async function checkUrl(url) {
  const apiUrl = `https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${API_KEY}`;

  const requestBody = {
    client: {
      clientId: "theantifishing",
      clientVersion: "1.0",
    },
    threatInfo: {
      threatTypes: ["MALWARE", "SOCIAL_ENGINEERING", "UNWANTED_SOFTWARE", "POTENTIALLY_HARMFUL_APPLICATION"],
      platformTypes: ["ANY_PLATFORM"],
      threatEntryTypes: ["URL"],
      threatEntries: [{ url: url }],
    },
  };

  try {
    const response = await fetch(apiUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(requestBody),
    });
    
    if (!response.ok) {
        console.error(`HTTP error! status: ${response.status}`);
        return false; 
    }
    
    const data = await response.json();
    
    if (data.matches && data.matches.length > 0) {
      return true; 
    }
    return false; 
  } catch (error) {
    console.error("Ошибка при проверке URL:", error);
    return false;
  }
}

chrome.webNavigation.onCompleted.addListener(async (details) => {
  if (details.frameId !== 0) { 
    return;
  }

  const url = details.url;
  console.log(`Проверка URL: ${url}`);
  const isThreat = await checkUrl(url);
  if (isThreat) {
      console.log(`URL ${url} является фишинговым!`);

    chrome.scripting.executeScript({
      
        target: { tabId: details.tabId },
        function: () => {
            const whitelist = JSON.parse(localStorage.getItem('whitelist')) || [];
            
            if (whitelist.includes(url)) {
              return;
            }

            document.body.innerHTML = '';
            document.body.style.cssText = 'margin: 0;';

            let div = document.createElement('div');
            let buttonAddToWhiteList = document.createElement('button');
            buttonAddToWhiteList.textContent = "Все равно перейти";
            buttonAddToWhiteList.style.cssText = 'border: none; background-color:rgb(41, 214, 119); border-radius: 5px; color: #f1faee; width: 10rem; height: 3rem; margin-top: 2rem; cursor: pointer;';
            buttonAddToWhiteList.addEventListener("click", function() {

              whitelist.push(url);
              localStorage.setItem('whitelist', JSON.stringify(whitelist));
              window.location.reload();

            });
            div.style.cssText = 'display: flex; margin: 0; justify-content: center; align-items: center; width: 100vw; height: 100vh; font-size: 30px; font-weight: bold; text-align: center; background-color: #e63946; color: #f1faee; font-family: Arial, Helvetica, sans-serif; flex-direction: column;';
            div.textContent = 'Сайт заблокирован из-за фишинга!';
            div.appendChild(buttonAddToWhiteList);
            document.body.append(div);
        },
    });
  } else {
    console.log(`URL ${url} безопасен.`);
  }
});

And here is my manifest.json :

{
  "manifest_version": 3,
  "name": "The Antifishing",
  "version": "1.0",
  "description": "The Antifishing - powerful chrome-based extension that helps users easily recognize fishing websites. It also contains a wide range of useful functions and has a user-friendly interface so everyone would be able to use it",
  "permissions": [
    "activeTab",
    "storage",
    "scripting",
    "tabs",
    "webNavigation"
  ],
  "host_permissions" : [
    "<all_urls>"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ],
  "action": {
    "default_popup": "popup.html",
     "default_icon": {
        "16": "images/icon-16.png",
        "32": "images/icon-32.png",
        "48": "images/icon-48.png",
        "128": "images/icon-128.png"
      }
  },
  "icons": {
    "16": "images/icon-16.png",
    "32": "images/icon-32.png",
    "48": "images/icon-48.png",
    "128": "images/icon-128.png"
  }
}

Im a 8th grade student and I’ve just started learning English, hope you can help with my problem.

Randomization of images+sentences in a within design without repetition of the same stimuli on Qualtrics

I have 30 photos of people and 30 sentences describing actions. I need to create randomized pairs where a photo is displayed with a sentence below it, and participants will evaluate the person depicted using a Likert scale. Participants must see all 30 pairs.

Participants should not see any repeated photos or sentences. I want the pairs to be randomized so that any photo can be paired with any sentence from the set. Additionally, I need to record which photo and sentence were shown for each evaluation in the dataset.

(I expect to have a certain amount of mid-dropout participants because the task is lengthy).

What approach can I use to achieve this?

So far, I created a block with a multiple-choice item using images and another item with sentences. I applied advanced randomization to ensure that only one of the image choices is displayed evenly. I did the same for the sentences. Then, I applied JavaScript to hide the image item, automatically select the displayed choice, and click the “Next” button (to save the response in the data). I repeated this process for the sentence item. Afterward, I piped the image and sentence into a Likert scale. All of this was set up within a block using loop & merge, repeating 30 times.

It is working well in my tests, but I am concerned that with real participants, some may drop out and disrupt the even distribution of the images and sentences. I fear that eventually some participants might see repeated images or sentences due to the disruption.

Would this be a real concern?

React how to Pass data from component to its sub-component

export const TriggerView = ({ children }: { children: React.ReactNode }) => {
    const [show, setShow] = useState(false);
    return <div>{children}</div>;
};

const Trigger = ({ children }: { children: React.ReactNode }) => {
    return <div>{children}</div>;
};

const View = ({ children }: { children: React.ReactNode }) => {
    return <div>{children}</div>;
};

TriggerView.Trigger = Trigger;
TriggerView.View = View;

I made a component TiggerView that have two sub-components Trigger and View the problem is that I want the show and setShow state to be passed to the sub-components but I don’t know how

How to domain match

I get from “Have I Been Pwned” API domain where breach occurred in this format:

{
 Domain: "adobe.com"
}

Now I also have my own data which contains urls to sites like “https://auth.services.adobe.com/en_US”, and I wanna match those url which leads to the same site as breach domain. But it’s not straightforward because maybe HIBP can return domain name like “bankofamerica.com” while I can have url like “bofa.com” which leads to the same site so these two should match. Is there some way to match domains like this?

Html content not updating immediately during loop in JavaScript

I’m creating a simple card game website where a player draws cards trying to get close but not exceeding a limit of points.

Ideally, when accepting a new card (through a confirm dialog box), the drawn card’s image should be displayed instantly. However, the images only appear after the loop finishes instead of immediately after each card is drawn.
In other words, all cards appear when I decline anymore cards (exiting the loop).

Any help or tips will be appreciated.

Here is the code:

Html:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Exercise Javascript</title>
        <link rel="stylesheet" type="text/css" href="exerciseStyle.css">
        <script src="match.js"></script>
    </head>
    <body>

        <!-- Header section -->
        <section class="header">
            <h1>Seven and a half game</h1>
        </section>

        <!-- Player input / before play button is clicked -->
        <section class="playerInput" id="before-input">
            <h2 id="playerInputHeader">Player Information</h2>
            <p><span class="label">Name:</span> <input type="text" id="Name" /></p>
            <p><span class="label">Initial Money:</span> <input type="text" id="Money" /></p>
            <div><button class="buttons" id="button_play" onclick="play()">Play!</button></div>
        </section>
        
        <!-- Player info / after play button is clicked -->
        <section class="playerInfo" id="after-input-play" style="display: none;">
            <h2 id="player-name"></h2>
            <p id="current-money"></p>
            <hr />
            <div class="button-container">
                <div><button class="buttons" id="button-new-game" onclick="round()">New Round</button></div>
                <div><button class="buttons" id="button-exit" onclick="reset()">End Game</button></div>
            </div>
        </section>

        <!-- Section where cards are displayed -->
        <section class="cardSection" id="cards-section" style="display: none;">
            <div class="cardSection" id="player-cards">
                <h3>Your Cards</h3>
                <div class="card-container" id="player-card-container" ></div>
            </div>
            <div class="cardSection" id="dealer-cards" >
                <h3>Dealer's Cards</h3>
                <div class="card-container" id="dealer-card-container"></div>
                </div>
            </div>
        </section>
  </body>
</html>

Javascript

var playerName = "";
var currentMoney = 0;
var playerCards = [];
var dealerCards = [];
var deck;
let initialdeck = [
    { value: 1, image: "cards/1.png" },
    { value: 2, image: "cards/2.png" },
    { value: 3, image: "cards/3.png" },
    { value: 4, image: "cards/4.png" },
    { value: 5, image: "cards/5.png" },
    { value: 6, image: "cards/6.png" },
    { value: 7, image: "cards/7.png" },
    { value: 0.5, image: "cards/8.png" },
    { value: 0.5, image: "cards/9.png" },
    { value: 0.5, image: "cards/10.png" },
    { value: 1, image: "cards/11.png" },
    { value: 2, image: "cards/12.png" },
    { value: 3, image: "cards/13.png" },
    { value: 4, image: "cards/14.png" },
    { value: 5, image: "cards/15.png" },
    { value: 6, image: "cards/16.png" },
    { value: 7, image: "cards/17.png" },
    { value: 0.5, image: "cards/18.png" },
    { value: 0.5, image: "cards/19.png" },
    { value: 0.5, image: "cards/20.png" },
    { value: 1, image: "cards/21.png" },
    { value: 2, image: "cards/22.png" },
    { value: 3, image: "cards/23.png" },
    { value: 4, image: "cards/24.png" },
    { value: 5, image: "cards/25.png" },
    { value: 6, image: "cards/26.png" },
    { value: 7, image: "cards/27.png" },
    { value: 0.5, image: "cards/28.png" },
    { value: 0.5, image: "cards/29.png" },
    { value: 0.5, image: "cards/30.png" },
    { value: 1, image: "cards/31.png" },
    { value: 2, image: "cards/32.png" },
    { value: 3, image: "cards/33.png" },
    { value: 4, image: "cards/34.png" },
    { value: 5, image: "cards/35.png" },
    { value: 6, image: "cards/36.png" },
    { value: 7, image: "cards/37.png" },
    { value: 0.5, image: "cards/38.png" },
    { value: 0.5, image: "cards/39.png" },
    { value: 0.5, image: "cards/40.png" }
];

function play() {
    // Get the name and the initial money.
    playerName = document.getElementById("Name").value;
    currentMoney = document.getElementById("Money").value;
  
    // Input check
    if (playerName.trim() === "" || currentMoney.trim() === "") {
      alert("Please enter both your name and initial money.");
      return;
    }
    currentMoney = parseFloat(currentMoney);
    if (isNaN(currentMoney)) {
        alert("Please enter a valid number for initial money.");
        return;
    }
  
    // Set variables
    deck = [...initialdeck];
    document.getElementById("player-name").textContent = playerName; 
    document.getElementById("current-money").textContent = `Total Money: ${currentMoney.toFixed(2)} €`;  
 
    // Adjust what to display after play button is pressed
    document.getElementById("before-input").style.display = "none";
    document.getElementById("after-input-play").style.display = "block";
    document.getElementById("player-name").style.display = "block";
    document.getElementById("current-money").style.display = "block";
    document.getElementById("cards-section").style.display = "block";
}

function reset() {
    // If player choses to end the game, show ending message
    alert(`You receive ${currentMoney.toFixed(2)} €. See you next time.`);

    // Reset all values
    let playerCardContainer = document.getElementById("player-card-container");
    playerCardContainer.innerHTML = "";
    let dealerCardContainer = document.getElementById("dealer-card-container");
    dealerCardContainer.innerHTML = "";
    playerName = "";
    currentMoney = 0;
    deck = [...initialdeck];
    document.getElementById("player-name").textContent = "";
    document.getElementById("current-money").textContent = "";

    // Display/hide elements
    document.getElementById("Name").value = "";
    document.getElementById("Money").value = "";
    document.getElementById("cards-section").style.display = "none";
    document.getElementById("after-input-play").style.display = "none";
    document.getElementById("before-input").style.display = "block";  

}

function round() {
    let keepGoing = true;
    let lost = false;
    bet = parseFloat(prompt("Money Bet:"));
    if (isNaN(bet)) {
        alert("Invalid bet: Your bet is not a number.");
         return;
    }else if (bet <= 0 || bet > currentMoney) {
        alert("Invalid bet amount. Please enter a positive number within your available funds.");
        return;
    }
  
    // Reset score and cards
    playerScore = 0;
    let playerCardContainer = document.getElementById("player-card-container");
    playerCardContainer.innerHTML = "";
    let dealerCardContainer = document.getElementById("dealer-card-container");
    dealerCardContainer.innerHTML = "";

    // Update current money
    currentMoney -= bet;
    document.getElementById("current-money").textContent = `Total Money: ${currentMoney.toFixed(2)} €`;  

    // Loop
    do {
        let card = getRandomCard();
        displayDrawnCard(card, 'player'); /////////////////////////////////////////// Issue here
        playerScore += card.value;
        if(playerScore > 7.5){
            lost = true;
        }
        if(!lost){
            keepGoing = confirm("Do you want another card?") /////////////// Cards are not shown until i decline this confirm.
        }
    } while (keepGoing && !lost);

    dealerTurn(playerScore, bet);
}
  
function getRandomCard() {
    if (deck.length === 0) return null;
    let randomIndex = Math.floor(Math.random() * deck.length);
    let card = deck[randomIndex];
    deck.splice(randomIndex, 1);
    return card;
}

function displayDrawnCard(card, type) {
    let cardContainer;
    if (type === 'player') {
        cardContainer = document.getElementById("player-card-container");
    } else if (type === 'dealer') {
        cardContainer = document.getElementById("dealer-card-container");
    }
    
    // Create a new img element for the card
    let cardElement = document.createElement("img");
    cardElement.src = card.image; // Use the image path stored in the card object

    // Add class to style
    cardElement.classList.add("Card");

    // Append the image to the container
    cardContainer.appendChild(cardElement);
}
  
function dealerTurn(playerScore, bet){
    let keepGoing = true;
    let dealer_lost = false;
    let dealerScore = 0;  
    
    // Check if dealer needs cards
    if(playerScore>7.5){
        keepGoing = false;
    }

    // Loop
    while (keepGoing && !dealer_lost){
        let card = getRandomCard();
        displayDrawnCard(card, 'dealer'); /////////////////////////////////////////// Same issue here
        dealerScore += card.value;
        if(dealerScore > 7.5){
            dealer_lost = true;
        }
        if(dealer_lost || playerScore < dealerScore){
            keepGoing = false;
        }
    }

    // Update data and show result
    if (dealer_lost) {
        currentMoney = currentMoney + 2*bet;
        document.getElementById("current-money").textContent = `Total Money: ${currentMoney.toFixed(2)} €`;  
        alert(`You have won ${bet.toFixed(2)} €. Remaining money: ${currentMoney.toFixed(2)} €.`);
    } else {
        document.getElementById("current-money").textContent = `Total Money: ${currentMoney.toFixed(2)} €`;  
        alert(`You have lost ${bet.toFixed(2)} €. Remaining money: ${currentMoney.toFixed(2)} €.`);
    }
    deck = [...initialdeck];
}

Just in case:
Css:

body{
   min-width: 600px;   
   max-width: 800px; 
   font-family:Verdana,Arial, serif;
   margin: auto;
   background-color: #04701824;
}

.header {
   font-family:Georgia, Times, serif;
   background-color:#047018;
   color: #FFFFFF;
   font-size: 120%;
   vertical-align: center;
   height: 100px;
}

h1{
   text-align: center;
   line-height: 100px;
}

h2{
   font-family:Georgia, Times, serif;
   font-style:italic;
   background-color:#0470189a;
   padding-left: 15px;
}

h3{
   font-family:Georgia, Times, serif;
   font-style:italic;
   background-color:#0470189a;
   padding-left: 15px;
}

input[type="text"] {
   width: 300px;
}

.buttons {
   min-width: 60px;
   height: 30px;
   background-color: #047018;
   color: white;
   padding:0 15px;
   border: none;
   border-radius: 15px;
   cursor: pointer;
   font-size: 14px;
   transition: background-color 0.3s ease;
 }

 .buttons:hover {
   background-color: #02420d;
 }


.button-container {
   display: flex;
   gap: 10px;
   justify-content: center;
}

hr {
   border: none;
   height: 3px;
   background-color: #047018;
   width: 100%;            
   margin: 5px auto;     
 }

 .Card {
   width: 80px;
   height: auto;
   margin: 8px;
   display: inline-block;
}

I tried everything, yet could not find the source of the issue. I don’t think I can post the images.

How to run Javascript when user clicks something from the Electron main menu using context bridge?

So the proper way to do ipc comms from main to renderer is via the context bridge right?

So what is the exact recommended way to do this from main to renderer? I have only done this in the reverse direction.

In my case, I want to open a modal dialog on the screen via jQuery when someone clicks a particular menu item.

Here’s a sample of what I am trying to do:

{
            label: 'Open Dialog',
            click: () => {
              $("#lastReadingViewer").show(); // this doesn't work - I get errors because I guess the jQuery doesn't exist in this context
              ipcMain.emit('openDialog'); // so now I think I need to use IPC, starting with this call, but now where do I put the code that listens for this event and then runs the jQuery?
            }
          }

yup-password had in older version minRepeating method – any alternatives?

yup-password module in older version, eg 0.2.2 had a nice method called minRepeating, which takes number argument to check the password for repeating letters, for example:

yup.minRepeating(2, "please don't repeat the same letter more than 2 times")

unfortunately in version 0.4.0 of this module the method gone and there is no alternative.

Do you know any other module or a nice way how to implement this kind of functionality with yup module in minimum version 1.6.1 ??

How to make an app/website visible to the user but hidden during screen sharing or video calls?

I want to implement a feature where my app or website is fully visible and interactive for the user but remains hidden or blocked when the user is screen sharing or in a video call. For example:

Similar to how Netflix blocks its video content during screen sharing, but the user can still watch it.

The app/website should detect screen sharing or recording attempts and either block the content or show a placeholder (e.g., blank screen) to others.

Are there any techniques, libraries, or best practices to achieve this? Any guidance or resources would be greatly appreciated!

published my framer website now every button when clicked says “You don’t have permission to access this page”

I am having an issue, with my framer website. I have published it, but now any button I press takes me to a page where it says “You don’t have permission
to access this page” while it works perfectly fine in the preview. I’ve never had this issue with my other websites, does anyone know why this might be?

In the preview it worked perfectly fine, but when published and looking at the websie when I press a button it just says “You don’t have permission
to access this page”.

Using fetch on a document without loading external javascripts and css

When I make a request to a document and check the Network Console tab, I see 5 additional requests that are (blocked:csp) that are from the initial page. This includes a googletagmanager script, gpt.js script and more.

I don’t see this behavior on ALL requests, but only on certain pages (example below).

Is there a way to block the rendering of HTML from a fetch or XML request and only load the document?

I am using a Chrome extension and running the code in the background.js

Here is a simple script that I am using:

async function fetchtest(type, url, url2=null){
        try {
            const fetchOpt = {
                method: type,
            }
            const response = await fetch(url, fetchOpt);
            if (!response.ok) {
              throw new Error(`Response status: ${response.status}`);
            }

            
      } catch (error) {
        console.error(error.message);
      }
}
fetchtest('GET', 'https://www.allrecipes.com/');

or

        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'https://www.allrecipes.com/', true);
        xhr.responseType='text';
        xhr.send();

Load either script and check your Network Console. Is there a way to prevent this?

How to work with leaflet polyline’s context menu

In Angular application, using “leaflet“: “^1.9.4” and “leaflet-contextmenu“: “^1.4.0”, I draw a graph where nodes are connected by sections.
For nodes I create markers

const options = {
  contextmenu: true,
  contextmenuInheritItems: false,
  contextmenuItems: this.buildMarkerContextMenu()
};
const marker = L.marker(coordinate, options);
(<any>marker).id = node.id;

for sections I create lines

const options = {
  contextmenu: true,
  contextmenuInheritItems: false,
  contextmenuItems: this.buildSectionContextMenu()
};
const line = L.polyline([coors1, coors2], options);
(<any>line).id = section.id;

After adding all nodes and sections I can iterate through map’s layers and see all nodes(markers) and sections(polylines) with their ids successfully.

Then in contextmenu callbacks for node

static showInformation(e: L.ContextMenuItemClickEvent) {
    console.log(e.relatedTarget);
    console.log((<any>e.relatedTarget).id);
}

all works properly, I get the node I clicked on.

Whereas for section I see the contextmenu, I can click menu items and get to their handlers but the same code somehow does not work: e.relatedTarget is undefined

static showSectionInformation(e: L.ContextMenuItemClickEvent) {
    console.log(e);
    console.log(e.relatedTarget); // !!! undefined
    //console.log((<any>e.relatedTarget).id);
}

All examples of leaflet-contextmenu show how to create menu for map and markers. Maybe for polyline something should be done differently?

Difficulty in using loader function in React-router 7.1.3

I am using react-router 7.1.3 and I am facing a problem with loader. I am not sure how to use it and the tutorial I am following is using react-router-dom which is very different from the new version.

App.jsx

import './App.css'
import { Routes, Route } from "react-router";
import Homepage from './Pages/HomePage/Homepage';
import Store from './Pages/Store/Store';
import About from './Components/About';
import Blog from './Components/Blog';
import Navbar from './Components/Navbar';
import SingleBook from './Pages/Store/SingleBook';

function App() {
  return (
    <>
      <Navbar />
      <Routes>
        <Route path='/' element={<Homepage />} />
        <Route path='/store' element={<Store />} />
        <Route path='/about' element={<About />} />
        <Route path='/blog' element={<Blog />} />
        <Route
          path='/book/:id'
          element={<SingleBook />}
          loader={({ params }) =>
            fetch(`http://localhost:3000/books/book/${params.id}`)}
        />
      </Routes>
    </>
  )
}

export default App

The poblem I am facing is in the route to /book/:id. The errors I am getting are:

Uncaught Error: useLoaderData must be used within a data router.  See https://reactrouter.com/en/main/routers/picking-a-router.
    at invariant (react-router.js?v=2d8b4bb3:1189:11)
    at useDataRouterState (react-router.js?v=2d8b4bb3:5492:3)
    at useLoaderData (react-router.js?v=2d8b4bb3:5552:15)
    at SingleBook (SingleBook.jsx:5:17)
    at renderWithHooks (chunk-VIJJTZRL.js?v=2d8b4bb3:11596:26)
    at mountIndeterminateComponent (chunk-VIJJTZRL.js?v=2d8b4bb3:14974:21)
    at beginWork (chunk-VIJJTZRL.js?v=2d8b4bb3:15962:22)
    at HTMLUnknownElement.callCallback2 (chunk-VIJJTZRL.js?v=2d8b4bb3:3680:22)
    at Object.invokeGuardedCallbackDev (chunk-VIJJTZRL.js?v=2d8b4bb3:3705:24)
    at invokeGuardedCallback (chunk-VIJJTZRL.js?v=2d8b4bb3:3739:39)

and in SingleBook.jsx I have written:

import { useLoaderData } from "react-router"

const SingleBook = () => {
  const { _id } = useLoaderData();
  return (
    <div>
      Single Book: {_id}
    </div>
  )
}

export default SingleBook

Package.json

{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "autoprefixer": "^10.4.20",
    "postcss": "^8.5.1",
    "prop-types": "^15.8.1",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-icons": "^5.4.0",
    "react-router": "^7.1.3",
    "swiper": "^11.2.1",
    "tailwindcss": "^3.4.17"
  },
  "devDependencies": {
    "@eslint/js": "^9.17.0",
    "@types/react": "^18.3.18",
    "@types/react-dom": "^18.3.5",
    "@vitejs/plugin-react": "^4.3.4",
    "eslint": "^9.17.0",
    "eslint-plugin-react": "^7.37.2",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-react-refresh": "^0.4.16",
    "globals": "^15.14.0",
    "vite": "^6.0.5"
  }
}

I have tried to search about it and nothing seems to be of help. Should I downgrade to react-router-dom or can I use the loader function in this version as well?

Streaming chunks of data from the server using React Javascript reader seems to be blocking until the input closes

I am new to frontend programming and am given a React codebase to update.

What I want to do is to allow the user to click a button to POST some data to the backend and continuously stream data from the backend and update the frontend continuously as data is being received. (For now, I’m console logging the received data) I’m using Javascript stream reader for this task.

The expected behaviour is that the data is console logged as each chunk of data is arrived but the observed behaviour is that the reader waits until the stream is closed and then console log the values. I can’t figure out why. In all examples I see, the reader continues after one chunk is received.

The following is the relevant code. It contains an upload button and a confirm button. Pressing confirm button submits the previously uploaded data.

const UploadActions = (props: UploadActionsProps) => {
    const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
    const [loadSwitch, setLoadSwitch] = useState<boolean>(false);
    const {authToken} = useContext(AuthTokenContext);
    const {
        isObjectUploaded, setIsObjectUploaded,
        uploadObjects, setUploadObjects,
        uploadObjectErr, setUploadObjectErr
    } = useContext(UploadObjectContext) as DataTransferObjectContextType;

    useEffect(() => {
        (async () => {
            // uploadObjects contain data uploaded in a previous page
            if (!isObjectUploaded || uploadObjects.length === 0 || !loadSwitch){
                return
            }

            const url = `/add/data`;

            const response = await fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'text/event-stream',
                    "authenticity-token": authToken
                },
                body: JSON.stringify(uploadObjects)
            });

            // Check if the response is a readable stream
            if (response.body) {
                const reader = response.body.getReader();
                const decoder = new TextDecoder();
                let done = false;
                // Read the stream in chunks and process them
                while (!done) {
                    const { value, done: doneReading } = await reader.read(); // this is where the problem is. The while loop does not continue until done is set
                    done = doneReading;

                    // Decode the chunk into text
                    const chunk = decoder.decode(value, { stream: true });
                    console.log(chunk)
                }

                console.log("done reading values")
            } else {
                console.error('Stream is not available');
            }
        })();
    }, [loadSwitch]);


    const handleUploadActions = (confirmation: boolean): void => {
        if (confirmation) {
            setLoadSwitch(true)
        }
        setIsDialogOpen(!isDialogOpen);
    }
   
    return (
                    {/* submit button. When this is pressed confirmation appears */}  
                    <Button
                        onClick={() => {setIsDialogOpen(true); setLoadSwitch(false)}}
                        sx={styles.saveButton}
                    >
                        <Typography sx={styles.buttonText}>Save Data</Typography>
                    </Button>
            {/* confirmation button. When clicked handleUploadActions function gets triggered */}
            {isDialogOpen &&
                <ConfirmationDialog
                    handleAction={handleUploadActions}
                    submitLabel={"Yes I'm sure"}
                    dialogTitle={"Are you sure you want to save?"}
                >
                </ConfirmationDialog>
            }
    );
};

export default UploadActions;

I’m just wondering if this is a problem with state handling in React. But I really don’t know how that can be.

Any help is appreciated.

–PPGoodman