Unexpected state loss when dynamically adding/removing nested hooks

Problem

I’m trying to build a dynamic questionnaire component in React where users can add and remove question sections. Each section manages its own local state using hooks (like useState or useReducer), and I store each section in an array of React components.

What I tried and expected

I have a QuestionSection component that manages its own state:

function QuestionSection({ id }) {
  const [answer, setAnswer] = useState("");
  return (
    <div>
      <h3>Question {id + 1}</h3>
      <p>Current answer: "{answer}"</p>
      <input 
        type="text" 
        value={answer} 
        onChange={(e) => setAnswer(e.target.value)} 
      />
    </div>
  );
}

And a parent component that manages the list:

function Questionnaire() {
  const [sections, setSections] = useState([0]); // array of section ids
  const add = () => setSections(prev => [...prev, prev.length]);
  const remove = () => setSections(prev => prev.slice(0, -1));

  return (
    <div>
      <button onClick={add}>Add Section</button>
      <button onClick={remove}>Remove Section</button>
      {sections.map(id => (
        <QuestionSection key={id} id={id} />
      ))}
    </div>
  );
}

Expected behavior: Only the removed section should reset; other sections should keep their entered data.

Actual behavior: When I remove a section (especially from the middle), other sections lose their state and reset to empty.

What I’ve tried

  • Ensured key={id} is unique and consistent
  • Tried using UUID for IDs instead of sequential numbers – same issue
  • Wrapped QuestionSection in React.memo() and added debug logs – state still resets

The keys appear stable in my logging, but React still resets the internal hook state. What am I missing about how React reconciliation works with dynamic lists?
Should I be lifting state up instead? Any recommended patterns?

Persistent storage denied for IndexedDB

I intend to use IndexedDB for local storage from local HTML.
However, the DB tables were deleted when free space on drive C: decreases to a certain level. For some reason browser won’t let me turn persistent storage ON neither on file:///, nor on localhost, nor on PWA. The same behavior with Notification.permission==granted for the page.
Using Chrome 137, W10-64.

Is there any way to solve the issue? Otherwise it makes IndexedDB useless and I do believe there is some workaround.

async function checkPersistentStorage() {
    if (!('storage' in navigator)) {
        console.log('NO Storage API support');
    }
    try {
        navigator.storage.persisted().then((isPersistent) => {
            console.log("Persistent storage: "+(isPersistent?"ON":"OFF"));
        });
        navigator.storage.persist().then((canPersist) => {
             console.log("Persistent storage can be enabled: "+(canPersist?"YES":"NO")); 
        });
    } catch (error) {
        console.error('Storage API ERROR:', error);
    }
}
checkPersistentStorage();
// console.log:
// Persistent storage: OFF
// Persistent storage can be enabled: NO

bug with font size inheritance

enter image description here

let newFont = 9;
let textSize = false;
let longText = false;
let shortText = false;
window.addEventListener('DOMContentLoaded', function() {
  // viewport attributes calculating
  const dpRatio = window.devicePixelRatio;
  const bodyWidth = Math.round(document.documentElement.getBoundingClientRect().width * dpRatio);
  const initScale = Math.round(1000000 / dpRatio) / 1000000;
  // replace viewport tag
  const docHead = document.head;
  const oldTag = document.querySelector('meta[name="viewport"]');
  const newTag = document.createElement('meta');
  newTag.setAttribute('name', 'viewport');
  newTag.setAttribute('content', 'width=' + bodyWidth + ', initial-scale=' + initScale);
  oldTag ? docHead.removeChild(oldTag) : null;
  docHead.appendChild(newTag);
  // apply new page width
  document.body.style.width = bodyWidth + 'px';
  // getting handles
  textSize = document.getElementsByClassName('textSize');
  longText = document.getElementById('longText');
  shortText = document.getElementById('shortText');
  // add font size changing cycle
  setInterval(function() {
    newFont = newFont + 1;
    if (newFont > 100) {
      newFont = 10;
    }
    // apply new font to whole page
    document.body.style.fontSize = newFont + 'px';
    // show real font sizes
    textSize[0].innerHTML = window.getComputedStyle(longText).fontSize;
    textSize[1].innerHTML = window.getComputedStyle(shortText).fontSize;
  }, 1000);
});
div {
  margin: 20px;
}

#longText {
  color: darkred;
}

#shortText {
  color: darkgreen;
  white-space: nowrap;
}

#redBox {
  width: 500px;
  height: 200px;
  font-size: 10px;
  color: white;
  background-color: red;
}

.textSize {
  border: 1px solid;
}
<div id="longText">
  <span class="textSize"></span> This long string should use inherited font size from Body, but real font size is bigger then Body have. This difference becomes very noticeable when the font size goes from 12px to 13px (instant jump). As the font size increases further to 80px, the difference becomes less obvious (smooth, no jump). After 80px to 100px both sizes are stable and correct.
</div>
<div id="shortText">
  <span class="textSize"></span> Correct inherited font size
</div>
<div id="redBox">
  500 * 200 px<br>
                  Make screenshot after zoom out on your <u>mobile device</u> and check my size
</div>

What i do wrong? Can i fix this problem with font size difference? I understand that dynamic changing of viewport tag is not good idea. But very interesting why two simple div-elements have two different sizes inherited from the same body parent element.

Note: this example works normal in desktop browsers, bug is reiterated on mobile browsers only (Chrome, Yandex and etc.).

Issue with Timezone Conversion in Angular Date Function

I’m working on an Angular project where I frequently use the JavaScript

new Date()

function. We have a new requirement to change the timezone of every date to match the timezone specified in the user’s account profile, regardless of the user’s location. To achieve this, I created a reusable function that adjusts the timezone of a date. Below is the implementation:

newTimezoneDate(dateVal?: any): Date {
    let timeZone: string | null = this.configService.getFacilityTimeZone();
    if (!timeZone) {
        timeZone = localStorage.getItem(facilityTimezone);
    }
    let date = new Date();
    if (dateVal) {
        date = new Date(dateVal);
    }
    if (!timeZone) {
        return date; // Return the date object if no timezone is provided
    }
    // Convert the date to the specified timezone
    const formattedDate = this.formatDateToTimeZone(date, timeZone);

    return formattedDate; // Return the converted date
}

formatDateToTimeZone(date: Date, timeZone: string) {
    try {
        return new Date(date.toLocaleString('en-US', { timeZone }));
    } catch (error) {
        // If an error occurs, use the default user's timezone
        return date;
    }
}

Problem
The function works as expected, but I’m facing an issue when I pass a date that has already been created using this function. In such cases, I receive incorrect date values.

Question
How can I modify the newTimezoneDate() function to correctly handle dates that have already been converted to the specified timezone? Is there a way to check if the date being passed is already in the correct timezone, so I can avoid unnecessary conversions?

Any help or suggestions would be greatly appreciated!

The fixedColumn feature not working in the datatable

I am trying to freeze the first four columns of this datatable when a user is scrolling it horizontally. But the style “dtfc-has-left” style=”position: relative;”” is not applied when I inspect the datatable in the browser, and the feature is not working. Please let me know if anyone can see the reason why the frozen columns is not applied. A minimal working example:

$(document).ready(function() {
  $('#example').DataTable({
    scrollX: true,
    scrollY: '300px',
    scrollCollapse: true,
    paging: true,
    fixedColumns: {
      leftColumns: 4
    },
    columnDefs: [{
      width: 120,
      targets: [0, 1, 2, 3]
    }]
  });
});
body {
  font-family: Arial, sans-serif;
  margin: 20px;
}

/* Container with fixed width and overflow-x auto for horizontal scrolling */
#tableContainer {
  border: 1px solid #ccc;
}

/* Prevent wrapping in table cells */
th,
td {
  white-space: nowrap;
  width: 120px;
}

/* Make DataTables scroll wrapper visible */
div.dataTables_wrapper {
  width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>


<!-- DataTables CSS -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" />
<link rel="stylesheet" href="https://cdn.datatables.net/fixedcolumns/4.2.2/css/fixedColumns.dataTables.min.css" />


<h2>DataTables FixedColumns Minimal Example</h2>

<div id="tableContainer">
  <table id="example" class="display nowrap" style="width:100%;">
    <thead>
      <tr>
        <th>Action</th>
        <th>Estimation ID</th>
        <th>Reference ID</th>
        <th>Project Name</th>
        <th>Status</th>
        <th>Result</th>
        <th>Change Requests</th>
        <th>Client Company</th>
        <th>Company Phone</th>
        <th>Company Email</th>
        <th>Client Budget (£)</th>
        <th>Material Value (£)</th>
        <th>Tender Value (£)</th>
        <th>MarkUp %</th>
        <th>Estimated Duration (Days)</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><button>Review</button></td>
        <td>101</td>
        <td>EST-001</td>
        <td>Bridge Construction</td>
        <td>Pile Setup Pending</td>
        <td>NAY</td>
        <td>2</td>
        <td>ABC Corp</td>
        <td>+1 555-1234</td>
        <td>[email protected]</td>
        <td>150,000.00</td>
        <td>75,000.00</td>
        <td>120,000.00</td>
        <td>25.00</td>
        <td>90</td>
      </tr>
      <tr>
        <td><button>Proceed</button></td>
        <td>102</td>
        <td>EST-002</td>
        <td>Building Renovation</td>
        <td>Valuation Pending</td>
        <td>NAY</td>
        <td>0</td>
        <td>XYZ Ltd</td>
        <td>+44 207-111-2222</td>
        <td>[email protected]</td>
        <td>NAY</td>
        <td>NAY</td>
        <td>NAY</td>
        <td>NAY</td>
        <td>NAY</td>
      </tr>
      <tr>
        <td><button>Review</button></td>
        <td>103</td>
        <td>EST-003</td>
        <td>New Warehouse</td>
        <td>Completed</td>
        <td>Won</td>
        <td>1</td>
        <td>MegaCorp</td>
        <td>+61 02-1234-5678</td>
        <td>[email protected]</td>
        <td>500,000.00</td>
        <td>200,000.00</td>
        <td>450,000.00</td>
        <td>15.00</td>
        <td>180</td>
      </tr>
    </tbody>
  </table>
</div>

<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<!-- DataTables JS -->
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<!-- FixedColumns JS -->
<script src="https://cdn.datatables.net/fixedcolumns/4.2.2/js/fixedColumns.dataTables.min.js"></script>

How to properly import Fabric.js in Vite (Vue 3 + Fabric 6.7) — Canvas is undefined

I’m building a Vue 3 project with Vite, and trying to integrate fabric.js (v6.7.0) to allow drawing on a PDF rendered using pdf.js.

Here’s the problem:
I installed fabric via:

npm install fabric 

Then I tried to import and use it like this:

import { fabric } from 'fabric' const canvas = new fabric.Canvas(...) 

But I get the following error in the browser:

Uncaught SyntaxError: The requested module 'fabric' does not provide an export named 'fabric' 

Then I tried other approaches like:

import fabric from 'fabric' 

But that gives me:

Uncaught SyntaxError: The requested module 'fabric' does not provide a default export 

Lastly, I tried:

import * as fabric from 'fabric' 

But then fabric.Canvas is undefined
(console.log(fabric) just gives a plain object with some utility properties — no Canvas constructor).

My environment:

  • Vue 3 (Composition API)
  • Vite 5
  • fabric.js 6.7.0
  • No TypeScript

What I’m trying to do:
I want to:

  1. Render the first page of a PDF using pdf.js
  2. Use fabric.js to allow the user to draw/sign on top of the canvas
  3. Export the result as an image or PDF

What’s not working:
Importing fabric.js in any of the standard ways doesn’t seem to give me access to fabric.Canvas, and I can’t find clear docs for usage with Vite.

Question:

  1. Has anyone successfully used fabric.js v6+ with Vite + Vue 3?
  2. What is the correct way to import and use fabric.Canvas in this setup?
  3. Do I need to use dynamic import() or configure Vite to handle CommonJS/ESM compatibility?

Any working examples or suggestions would be greatly appreciated!

this my full code:

import * as fabric from 'fabric'
import { jsPDF } from 'jspdf'
import html2canvas from 'html2canvas'
import * as pdfjsLib from 'pdfjs-dist'
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.mjs?worker'
pdfjsLib.GlobalWorkerOptions.workerPort = new pdfjsWorker()
pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjsLib.version}/pdf.worker.min.js`

const signDialog = ref(false)
const pdfCanvas = ref(null)
let fabricCanvas = null


const openSignDialog = async (path) => {
  try {
    signDialog.value = true

    const response = await api.get(`/api/doc/download/${path}`, { responseType: 'blob' })
    const blob = new Blob([response.data], { type: 'application/pdf' })
    const pdfData = await blob.arrayBuffer()

    const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise
    const page = await pdf.getPage(1)
    const viewport = page.getViewport({ scale: 1.5 })
    const canvas = pdfCanvas.value
    const context = canvas.getContext('2d')
    canvas.height = viewport.height
    canvas.width = viewport.width

    await page.render({ canvasContext: context, viewport }).promise

    fabricCanvas = new fabric.fabric.Canvas(canvas, { isDrawingMode: true })
    

  } catch (err) {
    console.error('load pdf error:', err)
  }
}

const exportSignedPDF = async () => {
  const canvasEl = pdfCanvas.value
  const image = await html2canvas(canvasEl)
  const imgData = image.toDataURL('image/png')
  const pdf = new jsPDF({
    orientation: 'portrait',
    unit: 'px',
    format: [canvasEl.width, canvasEl.height]
  })

  pdf.addImage(imgData, 'PNG', 0, 0, canvasEl.width, canvasEl.height)
  pdf.save('signed.pdf')
}

Struggling to auto-add product Y when product X is added to cart (Shopify)

First post here so please let me know if I’ve missed anything. Any help would be appreciated.

I have the below:
Become a Member (product)

  • Core Plan (variant)
  • Premium Plan (variant)
  • 1-week Trial (variant)

Bond (product)

  • Refundable (Core) (variant)
  • Refundable (Premium) (variant)
  • Refundable (Trial) (variant)

I want it so that if you add a Become a Member variant to cart (e.g. Core Plan), it auto adds Core variant of Bond automatically to cart.

And on a cart level (using drawer – but I know there can be limitations so happy to switch to page if easier).

Using Horizon Theme on Shopify.

With the help of Claude & ChatGPT I achieved the below, but most of the time only the bond gets added to cart.

I’m aiming to remove the “remove” button for the bond product in the cart so that if you remove the “Become a Member” it auto removes the Bond product.

TL;DR
Become a member ATC = Bond ATC (matched by variant ID)
Become a member Remove from Cart = Bond Remove from cart.

Any help would be appreciated

Product page snippet (I’ve put this in a custom liquid section on the Become a Member product template.

<script>
document.addEventListener("DOMContentLoaded", function () {
  const bondMap = {
//Format: Become a member variant ID: bond variant id
    46531128230137: 46586197672185, // Core 
    46531128262905: 46586197704953, // Premium
    46569399288057: 46586197737721, // Trial
  };

  const form = document.querySelector('form[action^="/cart/add"]');
  if (!form) return;
  
  const variantInput = form.querySelector('input[name="id"]');
  if (!variantInput) return;

  form.addEventListener("submit", function (e) {
    const selectedVariantId = parseInt(variantInput.value);
    const bondVariantId = bondMap[selectedVariantId];
    
    if (!bondVariantId) return;

    // Prevent the default form submission temporarily
    e.preventDefault();
    
    // First, add the main product
    fetch("/cart/add.js", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        items: [{ id: selectedVariantId, quantity: 1 }]
      }),
    })
    .then(response => response.json())
    .then(data => {
      // If main product was added successfully, add the bond
      return fetch("/cart/add.js", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          items: [{ id: bondVariantId, quantity: 1 }]
        }),
      });
    })
    .then(response => response.json())
    .then(data => {
      // Redirect to cart or refresh page as needed
      window.location.href = '/cart';
    })
    .catch(err => {
      console.error("Failed to add products:", err);
      // Fall back to normal form submission
      form.submit();
    });
  });
});
</script>

And on the cart liquid page:

{% comment %}{% unless item.product.tags contains 'Bond' %}{% endcomment %}
  <button
    class="button button--tertiary cart-items__remove"
    type="button"
    style="height:auto !important;"
    aria-label="{{ 'accessibility.remove_item' | t: title: item.title | escape }}"
    on:click="/onLineItemRemove/{{ item.index | plus: 1 }}"
  >
    <span
      class="button-unstyled pickup-location__button"
      style="
        font-size: 0.75rem;
        font-weight: 100;
        text-transform: none;
      "
    >Remove</span>
  </button>
{% comment %}{% endunless %}{% endcomment %}

{% if item.product.tags contains 'Subscription' %}
  <script>
    document.addEventListener("DOMContentLoaded", function () {
      const bondMap = {
        46531128230137: 46586197672185, // Core
        46531128262905: 46586197704953, // Premium
        46569399288057: 46586197737721, // Trial
      };

      function getLineItemKey(cartItems, variantId) {
        const item = cartItems.find(i => i.variant_id === variantId);
        return item ? item.key : null;
      }

      function removeBondIfOrphaned() {
        fetch("/cart.js")
          .then((res) => res.json())
          .then((cart) => {
            const items = cart.items;
            const variantIds = items.map((i) => i.variant_id);

            Object.entries(bondMap).forEach(([membershipId, bondId]) => {
              const hasMembership = variantIds.includes(parseInt(membershipId));
              const hasBond = variantIds.includes(bondId);

              if (!hasMembership && hasBond) {
                const bondKey = getLineItemKey(items, bondId);
                if (bondKey) {
                  fetch("/cart/change.js", {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify({
                      id: bondKey,
                      quantity: 0,
                    }),
                  })
                  .then(() => {
                    // Refresh cart drawer or reload page
                    if (typeof refreshCartDrawer === 'function') {
                      refreshCartDrawer();
                    } else {
                      window.location.reload();
                    }
                  })
                  .catch(err => console.error("Failed to remove bond:", err));
                }
              }
            });
          })
          .catch(err => console.error("Failed to fetch cart:", err));
      }

      // Listen for Remove button clicks using event delegation
      document.addEventListener('click', function(e) {
        if (e.target.closest('[on\:click^="/onLineItemRemove"]')) {
          // Delay to allow item removal to process
          setTimeout(removeBondIfOrphaned, 1000);
        }
      });
    });
  </script>
{% endif %}

Is there a way to make a code like this count every half an hour instead of every second?

Im trying to get a code that runs similair to this but instead of counting every second, it counts every half an hour. I’m using this on html because im going to need it to also be on other peoples devices that dont have any programs that could be used to run javascript, and html works on every device. This is what I have so far;

<html>

<head>
  <script type="text/javascript">
    function countup(startingdate, base) {
      this.currentTime = new Date()
      this.startingdate = new Date(startingdate)
      this.base = base
      this.start()
    }
    countup.prototype.oncountup = function() {}
    countup.prototype.start = function() {
      var thisobj = this
      this.currentTime.setSeconds(this.currentTime.getSeconds() + 1)
      var timediff = (this.currentTime - this.startingdate) / 1000
      var secondfield = Math.floor((timediff))
      var result = {
        seconds: secondfield
      }
      this.oncountup(result)
      setTimeout(function() {
        thisobj.start()
      }, 1000)
    }
  </script>
</head>

<body>
  <div id="holder"> </div>
  <script type="text/javascript">
    var startDate = new countup("June 22, 2025 15:30:00", "seconds")
    startDate.oncountup = function(result) {
      var mycountainer = document.getElementById("holder")
      mycountainer.innerHTML = +result['seconds']
    }
  </script>
</body>

</html>

I’d also be fine if theres a way to just times the given number by 1800 as it would also give me the numbers i need.
Thanks.

Webscraping Dynamically Loading Content [duplicate]

I am attempting to use the R package ‘rvest’ to webscrape the following page

https://www.superleague.co.uk/match-centre/report/43502

The issue is that I would like to scrape the page that loads when you click “teams” (to scrape the players and their positions) but also “player stats” (to scrape the players stats) but the URL is identical for both even though each page displays different data.

How would I go about scraping the data on these two pages when the URL is the same?

Thanks in advance

Can’t get a Calendar.js calendar to show up in ASP.NET razor page

I have an doctor patient app and I am trying to get a calendar to show up in one part of it using Calendar.js. I have tried a variety of solutions, and have been unable to get the events to show up.

Here is the Calendar.js file that I am using:

import { Calendar } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import listPlugin from '@fullcalendar/list';


document.addEventListener('DOMContentLoaded', function () {
    const calendarEl = document.getElementById('calendar');

    const calendar = new Calendar(calendarEl, {
            plugins: [dayGridPlugin, timeGridPlugin, listPlugin],
            initialView: 'dayGridMonth',
            headerToolbar: {
                left: 'prev, next today',
                center: 'title',
                right: 'dayGridMonth, timeGridWeek, listWeek'
            },
            events: [
                {
                    title: "Record a video for Marisa",
                    start: "2025-06-20"
                },
            ],
        });

        calendar.render()
});

Here is the Show.cshtml page where I am calling the calendar file from:

@using planMyMDVisit.Utilities
@model planMyMDVisit.Models.ViewModels.PatientEventViewModel

<style>
    #calendar-container {
        width: 80%;
        height: 500px;
        margin-left: auto;
        margin-right: auto;
        margin-top: -30px;
    }

    #calendar {
        height: 100%;
        width: 100%;
    }

    a.btn {
        margin-bottom: 5px;
    }
</style>

<div class="col s12 m7">
    <div class="card horizontal">
        <div class="card-content">
            <b><a class="bold red-text">Coronavirus/COVID-19 Alert</a></b>
            <p>If you have been in close contact with someone with coronavirus (COVID-19) and have experienced any of the following: fever, cough, diarrhea, cold/flu-like symptoms or have concerns that your current symptoms are related to COVID-19, please contact your doctor's office or local public health department for further direction.</p>
        </div>
    </div>
</div>

<p><strong>plan my MD visit</strong> is not for use in emergencies or for messages that require immediate attention. For medical or psychiatric emergencies, call 911 or go to the nearest hospital.</p>

<a asp-action="NewAppt" asp-controller="HealthCareTeams" class="waves-effect waves-light btn btn-primary" style="width: 200px;">Schedule an Appointment</a>

<br />
<br />

<a asp-action="Index" asp-controller="HealthCareTeams" asp-route-patient="@Model.Patient" class="waves-effect waves-light btn btn-primary" style="width: 200px;">View All</a>

<div class="row">
    <div class="col s12 m4 l4">

        <h5>Your Care Team</h5>

       @foreach (var hct in Helpers.TwoLatestCareTeams(Model.Patient))
        {
            <div class="card blue lighten-5">
                <div class="card-content black-text">
                    <span class="card-title truncate">@hct.Doctor.Specialty</span>
                </div>
                <div class="card-action">
                    @hct.Doctor.FullName()
                </div>
            </div>
        }

        </div>
        
    <div id="calendar-container">
        <div id="calendar"></div>
    </div>
 </div>


<script type="module" src="~/dist/Calendar.js"></script>

I have tried it with these file sitting over the above:

<script src="https://cdn.jsdelivr.net/npm/fullcalendar/[email protected]/index.global.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar/[email protected]/index.global.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar/[email protected]/index.global.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar/[email protected]/index.global.min.js"></script>

I also tried it with the type=”module” part deleted. I have read a lot from the docs of Calendar.js and using Chatgpt. Nothing is getting that event “Record a video with Marisa” even to show up………any help is appreciated.

Convert Chrome Extension into a Mobile App and add System-Wide Global Text Selection Context Menu Option using Mobile App

I have a chrome extension that I’m building with a TypeScript React Vite setup. It utilizes a Chrome API for creating a custom selection context menu. I want to port this chrome extension into a mobile app. Specifically, I want to be able to add a system-wide text selection context menu option, as shown in the images, which is the main reason I want to build an app. The WordReference app adds such an option when highlighting text in a browser. The WordReference app is not open in the background and is only installed on my Android 12 phone. It opens a popup in this case. I would like to redirect to my app or add a similar popup. Both options are viable.

An image showing the default text selection context menu on an Android 12 device

An image showing the custom text selection context menu option from the WordReference app

An image showing a WordReference popup when the context menu option is clicked

Why not use React Native or convert this into a PWA, you might ask? I do not want to create an entirely separate application that I have to test, maintain, style, and build. It seems largely unnecessary since my mobile app will be the exact same as the chrome extension, only with a few different APIs being used, which I will talk about later. When it comes to PWAs, as far as I know, it is impossible to modify the system-wide global context menu using a PWA.

Since this is a hobby/personal project that I want to open-source, I am perfectly content to sacrifice performance and native app feel in order to only have to maintain one single codebase. My chrome extension is not that large (but large enough to where I do not want to re-implement everything) and consists of only 5 pages. I do not expect to have many users using this app. Using a WebView-wrapped app seems like the ideal solution to this problem. There are some concerns about having an app that’s only a WebView wrapper being accepted to the app stores but I have read that some users have been able to submit their app successfully, despite it being just one big WebView.

In terms of options I have looked at, I have checked out Cordova (along with several third-party plugins), Ionic, Capacitor, and NativeScript, but none of these have straight forward APIs for what I need. The NativeScript docs talks about the ability to add java code to a NativeScript application, but I’m not sure if this is the simplest method to do this. I do not know much about native app development. For native Android apps, it appears that this Medium article describes how to change the context menu. I would prefer to be able to implement this app for both Android and iOS, but I am okay with only being able to implement it on Android. I do not have a Mac for XCode or iPhone to test my app on iOS anyway.

The only two APIs that I need for the mobile app that are different from the extension are Push Notifications (I am using the Web Push API in my extension) and the ability to add a global text selection context menu option like I was able to do with my chrome extension. The former has plenty of guides online for how to implement, but the latter does not.

I am not familiar with native app development at all and even if I was, I would not feel great about having to maintain two codebases that do the exact same thing only for the sake of using different APIs for two specific features.

How can I reuse as much chrome extension web code to make a cross-platform mobile app while adding a system-wide global text selection context menu option, similar to the one created by the WordReference app?

Why is CORS blocking my GET request to rails api? [duplicate]

I have an HTML/CSS/JS frontend that gets the user’s location when the page loads. It should also be getting the records in my Rails API. This is the response I get in the fetch request Response { type: "cors", url: "http://localhost:3000/moods", redirected: false, status: 200, ok: true, statusText: "OK", headers: Headers(3), body: ReadableStream, bodyUsed: false } index.js:34:24 The following is my frontend code to get location and fetch data:

function getLocation() {
  
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(success, error);
  }
  else {
    err.innerHTML = "Geolocation is not supported by this browser.";
  }

  return location;
}

function success(position) {
  
  currentLocation = [position.coords.latitude, position.coords.longitude]
  map.setView(currentLocation, 17);
}
  
function error(error) {
  console.log(error)
  err.innerHTML = "Please allow location permission to add a marker to the mood map";
}

function getMoods() {
  
  fetch("http://localhost:3000/moods")
  .then(res => console.log(res))
  .then(data => console.log(data))
}

// ---------------------------------------------------------------------------------------------

document.addEventListener("DOMContentLoaded", (event) => {
  getMoods();
  getLocation();
})

This is my rails routes.rb:

My rails routes.rb:Rails.application.routes.draw do
  resources :moods
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
  # Can be used by load balancers and uptime monitors to verify that the app is live.
  # get "up" => "rails/health#show", as: :rails_health_check

  # Defines the root path route ("/")
  # root "posts#index"
end

This is my rails MoodController.rb:

class MoodsController < ApplicationController
  def index
    moods = Mood.all
    render json: moods
  end

  def create
    mood = Mood.new(mood_params)

    if Mood.exists?(latitude: mood.latitude) and Mood.exists?(longitude: mood.longitude)
      render json: { message: "Record already exists" }
    elsif mood.save()
      render json: mood
    else
      render json: { message: "Error! Could not save!" }
    end
  end

    private

      def mood_params
        params.require(:mood).permit(:latitude, :longitude, :mood_description)
      end
end

This is my cors.rb:

# Be sure to restart your server when you modify this file.

# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin Ajax requests.

# Read more: https://github.com/cyu/rack-cors

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "*"
    resource "*",
      headers: :any,
      methods: [ :get, :post, :put, :patch, :delete, :options, :head ]
  end
end

I have tried making sure that the cors.rb is configured correctly and that the correct methods are being allowed. I don’t know if this is related, but in my frontend, when I click a button, I am able to make a POST request and store information in my rails backend. Here is the POST request:

fetch("http://localhost:3000/moods", {
        method: "POST",
        body: JSON.stringify({
          latitude: currentLocation[0],
          longitude: currentLocation[1],
          mood_description: moodDescription
        }),
        headers: {
          "Content-type": "application/json; charset=UTF-8"
        }
      });

I expected the fetch method’s data to be an array objects that I can then store in a variable or do whatever I want with it. Instead it is undefined. How can I get my GET request to work?

Sticky element with scroll-based top updates causes flickering or shaking on scroll

I’m building a scroll effect where .logo and .content elements inside .container blocks use position: sticky and also receive scroll-based top position adjustments using jQuery.

The goal is to keep these elements visually synced while scrolling through multiple full-height sections.

A smooth sticky scroll where .logo and .content stay perfectly in sync without visual shaking or micro-jumps.

Any advice or better way to handle this?

code – https://codepen.io/Rejuanul-Islam/pen/WbvLJwQ

Expo – Font still scalable after disabling globally

Im trying to disable font scaling globally across my app. Im aware of the risks which can come due to limiting accessibility. I’ve read different/conflicting answers, some mentioning to place the default prop code in App.tsx and others mentioning to place it in index.js.

After trying both, the font is still scalable on my device. Where am i going wrong?

App.tsx

import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View, TextInput } from 'react-native';
import { SafeAreaProvider } from "react-native-safe-area-context";
import Navigation from './Navigation';
import { useState, useEffect } from 'react';
import * as Font from 'expo-font';
import AppLoading from 'expo-app-loading';
import './TextConfig';

// @ts-ignore
if (Text.defaultProps == null) Text.defaultProps = {};
// @ts-ignore
if (TextInput.defaultProps == null) TextInput.defaultProps = {};
// @ts-ignore
Text.defaultProps.allowFontScaling = false;
// @ts-ignore
TextInput.defaultProps.allowFontScaling = false;


const getFonts = () => Font.loadAsync({

  'Gilroy-Regular': require('./assets/fonts/Gilroy-Regular.ttf'),
  'Gilroy-Bold': require('./assets/fonts/Gilroy-Bold.ttf'),
  'Gilroy-SemiBold': require('./assets/fonts/Gilroy-SemiBold.ttf'),
  'Gilroy-Light': require('./assets/fonts/Gilroy-Light.ttf'),
  'Gilroy-Medium': require('./assets/fonts/Gilroy-Medium.ttf'),
  'Gilroy-Thin': require('./assets/fonts/Gilroy-Thin.ttf'),
  

})

export default function App() {

  const [fontsLoaded, setFontsLoaded] = useState(false);
 
  if(fontsLoaded) {
    return <SafeAreaProvider><Navigation/></SafeAreaProvider> ;
  } else {
    return (<AppLoading startAsync={getFonts} onFinish={() => setFontsLoaded(true)} onError={console.warn} />)
  }
}

index.js

import { registerRootComponent } from 'expo';
import {Text, TextInput} from 'react-native';
import App from './App';


// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

Appsmith : Pass context payload from custum widget to JSObject

I am working with appsmith and have a very specific to which i cannot figure out the answer.

I have a custom widget in which there is a login form (i needed something very personalized):

HTML

<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" rel="stylesheet" />
<div id="background-container">
  <div class="centered-container">
    <div class="mimir-title">MIMIR DATABASE</div>
    <form id="login-form">
      <div class="form-group">
        <label for="handle">Handle:</label>
        <input type="text" id="handle" name="handle" autocomplete="username" required />
      </div>
      <div class="form-group">
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" autocomplete="current-password" required />
      </div>
      <button type="submit" class="submit-btn">Authentification</button>
    </form>
    <div id="example-hint" style="padding-top:1rem;color:#3c2657;font-size:1rem;max-width:360px;margin:auto;text-align:center;">
      
    </div>
  </div>
</div>

JS

appsmith.onReady(() => {
  const form = document.getElementById('login-form');
  if (form) {
    form.addEventListener('submit', function(e) {
      e.preventDefault();
      const handle = document.getElementById('handle').value;
      const password = document.getElementById('password').value;
      // Send the JS object to Appsmith, accessible via the widget's event response
      appsmith.triggerEvent("onSubmit", {handle, password});
    });
  }
});

Upon the click of the submit button, trigger the event “onSubmit”, to which i have defined the handler :

{{JSObject1.login}}

Is now triggers a function in a JSObject

export default {
  login: async (params) => {
    showAlert((params.handle));
  },
};

When i do not use the payload “params”, everything works fine. But when i try to pass the payload, i get a scope error :

console.ts:55 DataCloneError: Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope': function(){for(var n=arguments.length,t=new Array(n),A=0;A<n;A++)t[A]=arguments[A];if(!i.A.getExecutionMetaData...<omitted>...}} could not be cloned.
    at a (MessageUtil.ts:47:8)
    at S.respond (Messenger.ts:123:19)
    at evaluation.worker.ts:65:19

How can i get around this problem ?