React Dropdown Issue: All Dropdowns Open Simultaneously When Clicking Any Dropdown Button or External Elements

I am building a reusable dropdown component in React that can be toggled both by clicking its button and by clicking on external elements (watchElements). The dropdown works fine individually, but when I render multiple instances of the dropdown, clicking any button or external element toggles all dropdowns at once.

I want each dropdown instance to manage its own state independently so that clicking on a specific button or external element toggles only that specific dropdown.

Here is the current code for the Dropdown component:

import React, { useState, useEffect, useRef } from "react";

mport Button from "./Button";
import { RiArrowDownCircleFill } from "@remixicon/react";
import { constants } from "../constants";

const Dropdown = ({
  content,
  img = {
    url: "",
    alt: ""
  },
  img_classes = "",
  btn_classes = "",
  className = "text-center",
  content_wrapper_classes = "",
  btn_child = (
    <RiArrowDownCircleFill
      size={constants.static_sytles.icon_size_sm}
      className="text-white animate-bounce"
    />
  ),
  children,
  watchElements = []
}) => {
  const [isOpen, setIsOpen] = useState(false); 
  const dropdownRef = useRef(null);

  const toggleDropdown = (event) => {
    // console.log(dropdownRef.current)
    console.log('here is the event', event.target)
    console.log(dropdownRef.current.contains(event.target))
    if (dropdownRef.current && dropdownRef.current.contains(event.target)) {
      setIsOpen((prev) => !prev);
    }
  };

  const closeDropdown = () => {
    setIsOpen(false);
  };

  const handleClickOutside = (event) => {
    if (
      dropdownRef.current &&
      !dropdownRef.current.contains(event.target) &&
      !watchElements.some(
        (el) => el && el.contains && el.contains(event.target)
      )
    ) {
      closeDropdown();
    }
  };

  useEffect(() => {
    document.addEventListener("mousedown", handleClickOutside);

    if (watchElements.length) {
      watchElements.forEach((watchElement) => {
        if (watchElement) {
          watchElement.addEventListener("click", toggleDropdown);
        }
      });
    }

    return () => {
      document.removeEventListener("mousedown", handleClickOutside);

      if (watchElements.length) {
        watchElements.forEach((watchElement) => {
          if (watchElement) {
            watchElement.removeEventListener("click", toggleDropdown);
          }
        });
      }
    };
  }, [watchElements]);

  return (
    <div className={`${className}`}>
      <Button
        className={`${btn_classes} mt-3 flex items-center justify-center w-full`}
        // onClick={toggleDropdown}
        isDefault={false}
      >
        {btn_child}
      </Button>

      {/* Dropdown Menu */}
      <div
       ref={dropdownRef}
        className={` ${content_wrapper_classes} right-0 top-full w-full bg-white ring-opacity-5 overflow-hidden transition-all duration-700 ease-in-out ${
          isOpen  ?  "max-h-screen animate-flip-down" : "max-h-0"
        }`}
      >
        {img.url && (
          <img className={`${img_classes}`} src={img.url} alt={img.alt} />
        )}
        {children}
      </div>
    </div>
  );
};

export default Dropdown;

Usage:

const containerRefs= React.useRef([]);

{constants.home_services.map((service, index) => (
  <div
    className="w-full relative"
    key={index}
    ref={(el) => (containerRefs.current[index] = el)}
  >
    <div
      className={`${constants.static_sytles.bg_color_secondary_heavy} shadow-sm ${constants.static_sytles.shadow_secondary_heavy} px-4 py-2 rounded-md gap-y-2 border-2 border-[#9b9fab] flex justify-between items-center cursor-pointer`}
    >
      <h2 className="text-xl md:text-2xl font-light">{service.name}</h2>
      <img
        className="h-14 w-20 rounded-sm"
        src={service.ImgUrl}
        alt={service.name}
      />
    </div>
    <Dropdown
      watchElements={containerRefs.current}
      img={{ url: service.ImgUrl, alt: service.name }}
      btn_classes=""
      className=""
      img_classes="w-full rounded-sm h-80"
      content_wrapper_classes="rounded-lg"
      btn_child={
        <RiArrowDropDownFill
          className={`${constants.static_sytles.icon_color_secondary} ${constants.static_sytles.icon_size_sm}`}
        />
      }
    >
      <p className="mt-2 p-3">
        Lorem ipsum dolor sit amet consectetur, adipisicing elit. Accusantium,
        quisquam voluptatem odio sapiente mollitia, autem nostrum quod vitae
        aliquam quam non exercitationem consequatur architecto distinctio
        numquam rerum odit. Nemo, eaque!
      </p>
    </Dropdown>
  </div>
))}

I have tried to use the watchElements prop to hold the references of external elements to attach eventListners and use them to toggle dropdowns

why does switching to mobile layout make the javascript scroll button not work

when i’m at my desktop version it works normally as it should, scrolling up and down.
but when i go to the mobile version it doesn’t work as it should

<div class="everything">
            <div id="windowhof">
            </div>
            <div class="HOFcontainer">
                
                <div class="HOFdisplay">
                    
                    <div class="HOFchunks">
                       
                        <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
                     </div>
                    <div class="HOFchunks">
                       
                        <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
                     </div>
                     <div class="HOFchunks">
                        <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
                     </div>
                     <div class="HOFchunks">
                        <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
                     </div>
                     <div class="HOFchunks">
                        <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
    
                         <div class="HOFcards">
                            <img src="./hof/300px-ARS-pieces.png" alt="" width=" 175px">
                         </div>
                     </div>
                </div>
                <div class="buttonbox">
                    <button id="scrollUp" class="scrollButton">▲</button>
                    <button id="scrollDown" class="scrollButton">▼</button>
                  </div>
            </div>

and here’s the javascript

//HOF//

const scrollUpButton = document.getElementById("scrollUp");
const scrollDownButton = document.getElementById("scrollDown");
const display = document.querySelector(".HOFdisplay");
const rowHeight = display.querySelector(".HOFchunks").offsetHeight;

function scrollDisplay(direction) {
  display.scrollBy({ top: direction * rowHeight, behavior: "smooth" });
}

if (scrollUpButton && scrollDownButton) {
  scrollUpButton.addEventListener("click", () => scrollDisplay(-1));
  scrollDownButton.addEventListener("click", () => scrollDisplay(1));
}

i tried rewriting the javascipt and still doesn’t work. i thought it was something with the ID’s or something it also wasn’t it.

How can I maintain consistent formatting for the axis values in both the actual Plotly.js chart and the downloaded image?

I have provided the code where I am plotting 9 traces using Plotly.js and have
formatted the Y-axis scale by calling formatYAxisScale. However, the formatting is not retained when I download the image. Could I get some insight on why this is
happening and how I can ensure the Y-axis formatting is preserved in
the downloaded image?

import { Directive, ElementRef, Input, OnInit } from '@angular/core';
import * as Plotly from 'plotly.js-dist-min';
import { CurrencyFormatterPipe } from '../pipes/currency-formatter.pipe';

@Directive({
  selector: '[appUndiscountedCashflowPlotly]',
})
export class UndiscountedCashflowPlotlyDirective implements OnInit {
  @Input() public data: any;
  protected plot: any;

  constructor(
    protected elementRef: ElementRef,
    private currencyFormatterPipe: CurrencyFormatterPipe
  ) {}

  ngOnInit() {
    this.plot = this.elementRef.nativeElement;
    const labels = this.data.map((entry: { Year: any }) => entry.Year);

    const trace: any = [];

    const layout: Partial<Plotly.Layout> = {
      barmode: 'relative',
      showlegend: true,
      legend: {
        orientation: 'h', /// Horizontal layout
        yanchor: 'top', /// Align to the top of the legend box
        y: 1.4, /// Align legends to the bottom
        xanchor: 'left',
        x: 0, /// Align legends to the left
        font: { size: 12 },
        traceorder: 'normal',
      },
      autosize: true,
      xaxis: {
        title: {
          text: 'Year',
          font: { size: 14, color: '#000000' },
        },
        tickfont: { size: 12, color: '#000000' },
        tickmode: 'linear', /// Use linear mode for ticks
        dtick: 1, /// Set tick interval to 1
        fixedrange: true,
        showline: true,
      },     
      yaxis: {
        title: {
          text: 'Millions',
          font: { size: 14, color: '#000000' },
          standoff: 20, /// Adds space between the title and the axis
        },
        tickfont: { size: 12, color: '#000000' },
        showline: true,
        zeroline: true,
        fixedrange: true,
        automargin: true,
      },
      margin: {
        t: 30,
        b: 50,
        l: 30,
        r: 30, /// Adjust margins for better layout
      },
      dragmode: undefined, /// Disable dragging for zoom/pan
      plot_bgcolor: '#F8F8F8',
      paper_bgcolor: '#F8F8F8',
    };

    const options: Partial<Plotly.Config> = {
      responsive: true,
      displayModeBar: true,
      modeBarButtonsToRemove: [
        'select2d',
        'lasso2d',
      ] as Plotly.ModeBarDefaultButtons[], // Disable box and lasso select
      toImageButtonOptions: {
        format: 'png',
        filename: 'Cashflow Breakdown (Undiscounted)',
        width: 1000,
        scale: 1,
      },
      displaylogo: false,
    };

    Plotly.newPlot(this.plot, trace, layout, options)
    .then(() =>
      this.formatYAxisScale()
    );

    /// Listen to the relayout event
    this.plot.on('plotly_relayout', () => {
      this.formatYAxisScale(); // Reapply formatting after plot resize or layout change
    });

    /// Listen to the restyle(legend click) event
    this.plot.on('plotly_restyle', () => {
      this.formatYAxisScale(); // Reapply formatting after plot resize or layout change
    });
  }

  ///Format y axis currency
  private formatYAxisScale() {
    const yTicks = this.plot.querySelectorAll('.ytick text');
    yTicks.forEach((tick: any) => {
      const sanitizedValue = tick.__data__.text
        .replace(/−/g, '-')
        .replace(/,/g, '');
      const numericPart = parseFloat(sanitizedValue);
      const suffix = sanitizedValue.match(/[a-zA-Z]+/g)
        ? sanitizedValue.match(/[a-zA-Z]+/g)[0]
        : '';
  
      if (!isNaN(numericPart)) {
        if (suffix === 'M' && numericPart > 0 && numericPart < 1) {
          const convertedValue = numericPart * 1000; /// .2M => 200, .4M => 400
          tick.textContent = `${convertedValue}K`;
        } else if (suffix === 'M' && numericPart < 0) {
          const absoluteValue = Math.abs(numericPart);
          tick.textContent = `$(${absoluteValue})`; /// Display as $(6)
        } else {
          let formattedValue = this.currencyFormatterPipe.transform(
            numericPart,
            true,
            false,
            true,
            true
          );
          if (formattedValue !== null) {            
            if (suffix !== 'M') { /// For positive values with suffix, just add the suffix back
              formattedValue += suffix;
            }
            tick.textContent = formattedValue;
          }
        }
      }
    });
  }   
}

Glide.js execute script after API rewind is executed

I did setup a rail with Glide.js
I need to rewind it and then execute some other code but I don’t know how to stop my script untile rewind is completed

I did try with jQuery when since it returns an object but doesn’t seems to work

$.when( glideInstance.go('<<') ).done(function( e ) {
   alert("execute code");
});

react formik to have null as default value if input is cleared

In my reactjs i am using formik

const formikPaymentForm = useFormik({
  initialValues: {
    ezidebitAmount: null, // or undefined
  },
  validationSchema: Yup.object({
    ezidebitAmount: Yup.number().nullable(),
  }),

and in render

<TextField
            name="ezidebitAmount"
            label="Ezidebit Amount"
            type="number"
            autoComplete="off"
            error={Boolean(touched.ezidebitAmount && errors.ezidebitAmount)}
            helperText={touched.ezidebitAmount && errors.ezidebitAmount}
            onChange={formikPaymentForm.handleChange}
            value={formikPaymentForm.values.ezidebitAmount}
            fullWidth
            InputLabelProps={{
              shrink: true,
            }}
          />

and if i clear the input by deleting from keyboard then onSubmit it gives me the empty string which is not right as i have mentioned it number field

can we somehow set it to nul if i have cleared the value and type is number

i tried this

onChange={(event) => {
              const value = event.target.value;
              formikPaymentForm.setFieldValue(
                'ezidebitAmount',
                value === '' ? null : Number(value) // Transform empty string to undefined
              );
            }}

which is working fine, but i have so many forms in my application and i need a central solution for this

JS Variable outside function / global var [duplicate]

probably a stupid/very simple question but I can’t figure it out. I get the sat_id from an API, which I want to append to a new API URL. So I need the sat_id outside the function. How do I do that?
Here is the script:

const sat_url = 'https://api.wheretheiss.at/v1/satellites';
let sat_id = 0; 

async function getISS() {
           const response  = await fetch(sat_url);
           const data = await response.json();
           sat_id = data[0].id;
           console.log(sat_id); //here is the correct sat_id
}

getISS();

console.log(sat_id); //here ist the "0" from above but not the sat_id from the function

const data_url ='https://api.wheretheiss.at/v1/satellites/' + sat_id;

translate by google 🙂

Swiper.js not working properly, when looped and slidesPerView is given (React.js)

I am using swiper.js/react, I had to create a carousal having next and prev buttons to move single slide, also need to add buttons to move the pages by the given breakpoints in the code (in accordance with slidesPerView).

next and prev buttons work perfectly but the Dots / Page buttons not working properly when carousal has looped & slidesPerView, slideToLoop sometimes move only one slide (this is the main issue), even if added a static value to move to slideToLoop 0.

Please find the code below


import { Swiper, SwiperSlide } from "swiper/react";
import ArtistCard from "../songcards/artistcard";
import { useEffect, useRef, useState } from "react";
import { NextSvg, PrevSvg } from "../../assets/svg";

const Dots = ({ swiperRef, slideLength }) => {
  const [index, setIndex] = useState(0);
  const [pageLength, setPageLength] = useState(0);

  const changeSlide = (index) => {
    const newIndex = Math.floor(
      (swiperRef.current.slides.length / pageLength) * index
    );
    swiperRef.current?.slideToLoop(newIndex);
  };

  useEffect(() => {
    const swiperEle = swiperRef.current;

    const handleChange = () => {
      const curr_index = swiperEle.realIndex;
      const per_page = Math.floor(slideLength / pageLength);
      setIndex(Math.floor(curr_index / per_page));
    };

    if (swiperEle) swiperEle.on("slideChange", handleChange);

    return () => {
      if (swiperEle) swiperEle.off("slideChange", handleChange);
    };
  }, [swiperRef, pageLength, slideLength]);

  useEffect(() => {
    const updateSlidesPerView = () => {
      const width = window.innerWidth;
      if (width >= 1024) {
        setPageLength(slideLength / 6);
      } else if (width >= 768) {
        setPageLength(slideLength / 4);
      } else if (width >= 640) {
        setPageLength(slideLength / 3);
      } else {
        setPageLength(slideLength / 2);
      }
    };
    updateSlidesPerView();

    window.addEventListener("resize", updateSlidesPerView);

    return () => {
      window.removeEventListener("resize", updateSlidesPerView);
    };
  }, [slideLength]);

  if (pageLength)
    return (
      <>
        {Array.from({ length: pageLength }).map((_, dotIndex) => (
          <span
            key={dotIndex}
            className={`${dotIndex} cursor-pointer h-[4px] inline-block rounded-sm transition-all duration-300 ${
              dotIndex === index ? "w-5 bg-white" : "w-[10px] bg-[#c0c0c0]"
            } ${dotIndex === pageLength - 1 ? "" : "mr-[10px]"}`}
            onClick={() => changeSlide(dotIndex)}
          ></span>
        ))}
      </>
    );
};

const HeroArtistsCarousal = () => {
  const swiperRef = useRef(null);

  const handleNext = () => swiperRef.current && swiperRef.current.slideNext();
  const handlePrev = () => swiperRef.current && swiperRef.current.slidePrev();

  const artists = [...];

  return (
    <div className="relative">
      <Swiper
        spaceBetween={30}
        slidesPerView={2}
        loop={true}
        breakpoints={{
          640: {
            slidesPerView: 3,
          },
          768: {
            slidesPerView: 4,
          },
          1024: {
            slidesPerView: 6,
          },
        }}
        onSwiper={(swiperInstance) => {
          swiperRef.current = swiperInstance;
        }}
      >
        {artists.map((artist) => (
          <SwiperSlide key={artist.id}>
            <ArtistCard artist={artist} />
          </SwiperSlide>
        ))}
      </Swiper>
      <div className="mt-7 flex justify-end items-center">
        <Dots swiperRef={swiperRef} slideLength={artists.length} />
        <button
          onClick={handlePrev}
          className="hidden 2lg:inline-block ml-[10px]"
        >
          <PrevSvg className="fill-white hover:fill-[#25a56a] w-[30px] h-[30px] transition-colors duration-300" />
        </button>
        <button onClick={handleNext} className="hidden 2lg:inline-block">
          <NextSvg className="fill-white hover:fill-[#25a56a] w-[30px] h-[30px] transition-colors duration-300" />
        </button>
      </div>
    </div>
  );
};

export default HeroArtistsCarousal;

Any help would be appreciated!

Change timezone

I have this script but right now it’s for NL, I tried to change it for Portugal timezone but immediately stops and doesn’t work. At the same time, I want my nav bar to appear as a display “block” but it’s not working, I have my nav with “display: block:” but I’m not seeing any proper effect. So I don’t know what I’m doing wrong, that’s my first proper coding project by the way 🙂

html {
    background: #fffff2;
    /* background: #FCF9E2; */
    color: #3f3e3e;
    font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
}

nav {
    position: absolute;
    right: 1rem;
    top: 1rem;
}

nav {
    display: block;
    unicode-bidi: isolate;
    font-size: 22px;
}

ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
     
  }

  * {
    scrollbar-width: none;
}

footer {
    position: fixed;
    bottom: 0;
    z-index: 10;
    width: calc(100vw - 2rem);
    background-color: inherit;
    border-top: var(--border);
    padding: 1rem;
}



p {
    text-align: center;
    padding: 24rem;
    font-size: 50px;
   
  }

  a {
    color:  #3f3e3e;
    text-decoration: none;
}
  
a:hover {
    color:#00A0C6; 
    text-decoration: green wavy underline;
    cursor:pointer;  
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Casa Estrela</title>
    <link rel="stylesheet" href="/style.css">
</head>





<body>

    <header>
      
<p>Casa Estrela: is a non-profit organization and self-organised space for artists, theoreticians, researchers, practitioners, and activists. This is an ongoing process of building a place for creation, study, connection and exchange for everyone who wants to expand their own practice. The house is located in the village of Molelos, in Beira alta Portugal, next to Serra do Caramulo. We have been developing projects and receiving people since 2023. To stay with us, there is no need to apply, nor is there any selection. We practice solidarity prices, as accessible as possible. </p>
        <nav>
            <ul class="nobull">
              <li></li>
                    <li><a href="https://www.instagram.com/casaestrela__/" target="”_blank”">↳ instagram</a></li>
                    <li><a href="mailto:[email protected]" target="”_blank”">↳ email</a></li>
                      </ul>
          <nav>
            </nav></nav>


    </header>
<footer>
    <div>

       It is
       <span id="time">14:24:24</span>
       on a
       <span id="day">Monday</span>
       in Molelos (PT) right now
       <span id="activity">and probably the cats are sleeping</span> 


    </div>
</footer>
<script>
    /* ---------------------------- CLOCK ---------------------------- */
    
    var d = new Date();
    var t = d.toLocaleTimeString("nl-NL", {timeZone: "Europe/Amsterdam"});
    
    
    function clock() {
      var d = new Date();
      var t = d.toLocaleTimeString("nl-NL", {timeZone: "Europe/Amsterdam"});
      document.getElementById("time").innerHTML = t;
    }
    
    document.addEventListener("DOMContentLoaded", () => {
        clock();
        setInterval(clock, 1000);
    });
    
    
    /* ---------------------------- DAY OF THE WEEK ---------------------------- */
        const weekday = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
    
        // const d = new Date();
        let day = weekday[d.getDay()];
        document.getElementById("day").innerHTML = day;
    /* ---------------------------- SCHEDULE ---------------------------- */
    
      console.log(t);
    
      let activity;
      if (t < "09:00:00") {
        activity = "";
        link = ""
      }
        else if (t > "10:00:00" && t < "18:00:00" && "Monday, Tuesday, Wednesday, Thursday, Friday".includes(day)) {
      activity = "and Kirsten should be working";
      document.getElementById("circle").style.backgroundColor="#0000ff";
        }
        else if (t > "09:00:00" && t < "10:00:00" && "Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday".includes(day)) {
      activity = "and Kirsten is probably *essaying*";
      document.getElementById("circle").style.backgroundColor="#00ff00";
        }
      
      else {
        activity = "";
        link = ""
      }
      document.getElementById("activity").innerHTML = activity
    
    
    /* ---------------------------- TIME STAMP ---------------------------- */
    function getTimeStamp() {
         var now = new Date();
         return (now.getHours() + ':' + ((now.getMinutes() < 10) ? ("0" + now.getMinutes()) : (now.getMinutes())) + ':' + ((now.getSeconds() < 10) ? ("0" + now
                       .getSeconds()) : (now.getSeconds())));
    }
    
    
      function timeIn() {
          document.getElementById('timeIn').value = getTimeStamp();
      }
      function timeOut() {
          document.getElementById('timeOut').value = getTimeStamp();
      }
    
      function duration() {
        var diff = timeOut - timeIn; //milliseconds interval
        document.getElementById('duration').value = diff;
      }
    
 </script>
 <script>
    /* ---------------------------- DARKMODE ---------------------------- */
    
    let timeofday
    if (t < "07:00:00") {
      document.body.style.backgroundColor="#111";
      document.body.style.color="#fff";
    }
    else if (t < "20:30:00") {
      document.body.style.backgroundColor="inherit";
      document.body.style.color="inherit";
    }
    else {
      document.body.style.backgroundColor="#111";
      document.body.style.color="#fff";
    }
    </script>


    
</body>




</html>

Issue with Hover Functionality on 3D Surface Plot in Plotly version 2.35.2

I am experiencing an issue with the hover functionality on a 3D surface plot created using Plotly.
When I move my mouse over the edges of the graph, it seems like the hover interaction does not extend fully to cover all the data. As a result, some data points near the edges are not accessible or visible through the hover template.
Is this a known issue with Plotly 3D surface plots?
Are there any specific adjustments or workarounds to ensure hover functionality works correctly for edge data points?In the code, if sizex is 3 and sizey is 500, we can access the data just fine, but when we change sizex to 3 and sizey to 1000, I encounter an issue where it doesn’t display the hovertext.

Plotly version am using: 2.35.2
Here is the code;

function generateMockData(sizex, sizey) {
const z_data = ;
const x_data = ;
const y_data = ;

for (let i = 0; i <= sizey; i++) {
const row = ;
for (let j = 0; j <= sizex; j++) {
row.push(0);
}
z_data.push(row);
}

for (let i = 0; i <= sizey; i++) {
const row = ;
for (let j = 0; j <= sizex; j++) {
row.push(j);
}
x_data.push(row);
}

for (let j = 0; j <= sizey; j++) {
y_data.push(j);
}

return { x_data, y_data, z_data };
}

// Below settings works fine:
sizex = 3;
sizey = 500;

// Below settings causes hovertext dosen’t display for x=3
//sizex = 3;
//sizey = 1000;

const { x_data, y_data, z_data } = generateMockData(sizex, sizey);

const trace = {
type: ‘surface’,
x: x_data,
y: y_data,
z: z_data,
hovertemplate: ‘X: %{x}
Y: %{y}
Z: %{z}’,
colorscale: ‘Viridis’
};

const layout = {
title: ‘Mock Data 3D Surface’,
autosize: false,
width: 800,
height: 800,
margin: {
l: 65,
r: 50,
b: 65,
t: 90
},
scene: {
xaxis: { title: ‘X Axis’ },
yaxis: { title: ‘Y Axis’ },
zaxis: { title: ‘Z Axis’ }
}
};

// Render grafen
Plotly.newPlot(‘myDiv’, [trace], layout);`:

link to the code: https://codepen.io/fardoos020202/pen/XJrNKJQ

I have tried various solutions and read through their forums but haven’t had any luck finding a fix for this issue.

Does the Navigate “to” support second level forwarding?

Here is my app.jsx

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import OTAndTOGateKeeper from "./components/otAndTO/OTAndTOGateKeeper.jsx";
import LoginForm from "./components/otAndTO/LoginForm.jsx";
 return (
    <Router>
      <Routes>
        <Route path='/otAndTO' element={<OTAndTOGateKeeper/>}>
          <Route index element={<OTAndTOForm/>}/>
          <Route path="login" element={<LoginForm/>}/>
        </Route>        
      </Routes>
    </Router>
);

Here is my OTAndTOGateKeeper.jsx

import { Navigate } from 'react-router-dom';
import OTAndTOForm from "./OTAndTOForm.jsx";
export default function OTAndTOGateKeeper(){
    let finalComponent;
    if (sessionStorage.getItem("accessToken")){
        finalComponent=<OTAndTOForm/>
    } else {
        finalComponent=<Navigate to="/otAndTO/login"/>
    }
    return finalComponent;
}

When I Browse the ‘/otAndTO’, it should return the login form; unfortunately, there are nothing return.

However, when I change the App.jsx and OTAndTOGateKeeper.jsx as the following:

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import OTAndTOGateKeeper from "./components/otAndTO/OTAndTOGateKeeper.jsx";
import LoginForm from "./components/otAndTO/LoginForm.jsx";
 return (
    <Router>
      <Routes>
        <Route path='/otAndTO' element={<OTAndTOGateKeeper/>}>
          <Route index element={<OTAndTOForm/>}/>
        </Route>
        <Route path="login" element={<LoginForm/>}/>            
      </Routes>
    </Router>
);

import { Navigate } from 'react-router-dom';
import OTAndTOForm from "./OTAndTOForm.jsx";
export default function OTAndTOGateKeeper(){
    let finalComponent;
    if (sessionStorage.getItem("accessToken")){
        finalComponent=<OTAndTOForm/>
    } else {
        finalComponent=<Navigate to="/login"/>
    }
    return finalComponent;
}

It works as expected.

Here is my environment:

"react-dom": "^18.3.1",
"react": "^18.3.1",
"react-router-dom": "^7.0.2"

I am using the vite+react development tools.

Is the only work for the top level URL?

Get S3 Directory Names

I am trying to write up some JavaScript to connect to S3 and spit out all of the directory names inside of the “data” folder. There are hundreds of “data/id=___” folders and I am trying to get a list of all of them.

From the research I have been attempting it seems like I may have to use SetDelimiter but I am pretty new to all of this and cannot quite figure it out. Can anyone point me in the right direction?

var AmazonS3ClientBuilder= Packages.com.amazonaws.services.s3.AmazonS3ClientBuilder;
var ListObjectsV2Request = Packages.com.amazonaws.services.s3.model.ListObjectsV2Request;
var bucketName = 'filtered-data';
var objectPath = 'data/id='

var s3Client = AmazonS3ClientBuilder.standard().withRegion("us-east-2").build();

Animating ball around SVG path in React-Native

I am trying to animate a ball around an SVG path in React-Native and am not quite sure how to go about doing it, the SVG path is a pill shape.

Here is my code so far,

<View style={styles.container}>
  <Svg height="500" width="400">
    <Path
      d="M80 340 A100 100 0 0 0 320 340 v-200 A100 100 0 0 0 80 140 v200"
      fill="none"
      stroke="#eaeaea"
      strokeWidth="16"
    />
    <Circle cx="320" cy="160" r="16" fill="#512468" />
  </Svg>
</View>

And here is a visual:

Environment Visual

Toast Notification not showing when replacing file in Upload Compnent of Ant design

I’m using Ant design’s Upload Component in my code to enable file uploading and replacing.
Following are the props I’ve defined:

const uploadProps = {
    beforeUpload: async (file) => {
      // console.log('file:', file);
      const allowedExtensions = ["pdf", "docx"];
      const fileExtension = file.name.split(".").pop().toLowerCase();
      if (file.size < 50 * 1000) {
        toast.error('CV file must be at least 50KB in size', {theme: 'dark'})
      } else if (file.size > 5 * 1000000) {
        toast.error('CV file cannot exceed 5MB in size', {theme: 'dark'})
      } else {
        if (!allowedExtensions.includes(fileExtension)) {
          toast.error("Only PDF and DOCX files are allowed.", {theme: 'dark'});
          return Upload.LIST_IGNORE;
        }

        if (isUploaded) {
          toast.error("You can upload only one CV at a time.", {theme: 'dark'});
          return Upload.LIST_IGNORE;
        }
        setLoading(true);

        try {
          const formData = new FormData();
          formData.append("file", file);
          const response = await axios.post(
            `${import.meta.env.VITE_BASE_URL_PYTHON}/upload-cv`,
            formData,
            {
              headers: {
                "Content-Type": "multipart/form-data",
              },
            }
          );

          if (response.status === 200 && response.data) {
            const { _id } = response.data.data;
            const { fileSize } = response.data.data;
            setUserData({ fileSize: fileSize });
            setRecordId(_id);
            setUploadedFile(file.name);
            setIsUploaded(true);
            setLoading(false);
          } else {
            toast.error("Failed to upload CV. Please try again.");
            setLoading(false);
          }
        } catch (error) {
          toast.error("An error occurred while uploading.");
          console.error("Upload error:", error);
        }
      }

      return false; // Prevent automatic upload
    },
  };

  const handleDeleteCV = () => {
    setUploadedFile(null); // Remove uploaded file name
    setRecordId(null); // Reset record ID
    setIsUploaded(false); // Mark as not uploaded
    // toast.info("CV has been deleted.");
  };

  const replaceProps = {
    beforeUpload: async (file) => {
      const allowedExtensions = ["pdf", "docx"];
      const fileExtension = file.name.split(".").pop().toLowerCase();

      if (file.size < 50 * 1000) {
        toast.error('CV file must be at least 50KB in size', {theme: 'dark'})
        console.log('replace prop min size');
        return Upload.LIST_IGNORE;
      } else if (file.size > 5 * 1000000) {
        toast.error('CV file cannot exceed 5MB in size', {theme: 'dark'})
        console.log('replace prop max size');
        return Upload.LIST_IGNORE;
      } else if (!allowedExtensions.includes(fileExtension)) {
        toast.error("Only PDF and DOCX files are allowed.", {theme: 'dark'});
        return Upload.LIST_IGNORE;
      } else {
        if (isUploaded) {
          handleDeleteCV();
        }
        setLoading(true);

        try {
          const formData = new FormData();
          formData.append("file", file);
          const response = await axios.post(
            `${import.meta.env.VITE_BASE_URL_PYTHON}/upload-cv`,
            formData,
            {
              headers: {
                "Content-Type": "multipart/form-data",
              },
            }
          );

          if (response.status === 200 && response.data) {
            const { _id } = response.data.data;
            setRecordId(_id);
            setUploadedFile(file.name);
            setIsUploaded(true); // Mark CV as uploaded
            // toast.success("CV uploaded successfully!");
            // handleDeleteCV()
            setLoading(false);
          } else {
            toast.error("Failed to upload CV. Please try again.", {theme: 'dark'});
            setLoading(false);
          }
        } catch (error) {
          toast.error(error || "An error occurred while uploading.", {theme: 'dark'});
          console.error("Upload error:", error);
          setLoading(false);
        }
        return false; // Prevent automatic upload
      }
    },
  };

But the issue I’m facing is that the error toast appears if the selected file isn’t a valid (less than 50kb or greater than 5MB) while uploading (using ‘uploadProps’) but it doesn’t work when I try to replace the file with invalid file (using ‘replaceProps’). Instead, all the error toasts show up when I select a valid file.

I made two different props for uploading and replacing.
I tried to add

return false 

and

return Upload.LIST_IGNORE 

but didn’t work.

Open pdf in pdf-js viewer from streamlit app

I have a streamlit app, and I want it to display a pdf in an iframe. My functionality requirements for my pdf viewer/iframe are:

  • I want the pdf to open to a particular (parameterizable) page
  • I want the pdf to open with particular (parameterizable) quote/text already highlighted
  • I want the pdf to be scrollable in the viewer/iframe

The requirements above led me to go with (or attempt to go with) pdf.js, instead of the streamlit-pdf-viewer custom component.

I’ve stripped down my streamlit app to the following minimal app.py, which just includes three buttons/links that unsuccessfully attempt to display the pdf viewer per my requirements:


import streamlit as st
import streamlit.components.v1 as components
import urllib

def main():
    st.title("Hello from streamlit-n-pdfjs!")

    # Locations of my (public) r2 bucket and the document in it I want to view
    BUCKET_URL = "https://pub-ec8aa50844b34a22a2e6132f8251f8b5.r2.dev"
    DOCUMENT_NAME = "FINAL_REPORT.pdf"

    # "Media stored in the folder ./static/ relative to the running app file is served at path app/static/[filename]"
    # ^ https://docs.streamlit.io/develop/concepts/configuration/serving-static-files
    local_pdfjs_path = "./app/static/pdfjs-4-9-155-dist/web/viewer.html"

    # Attempt to link to the doc using the pdf.js viewer
    PAGENUM = 100
    HIGHLIGHT_QUOTE = 'exited the stage'
    ENCODED_QUOTE = urllib.parse.quote(HIGHLIGHT_QUOTE)
    FULL_DOC_URL = f"{BUCKET_URL}/{DOCUMENT_NAME}#page={PAGENUM}&search={ENCODED_QUOTE}"

    pdfjs_viewer = f"{local_pdfjs_path}?file={FULL_DOC_URL}"

    # Clicking the link below opens the correct pdf to the correct page, but does not search/highlight the quote text,
    # ...and of course does not open in an iframe
    st.markdown(f"[link to the doc I can't get to open in iframe w/ buttons below]({FULL_DOC_URL})") # opens doc in a new tab but doesn't search/highlight quote

    # clicking button below says "404: Not Found"
    if st.button("Show PDF in iframe with highlights, via pdfjs viewer"):
        components.iframe(pdfjs_viewer, height=800, scrolling=True)

    # Clicking the button below opens an iframe border, but 
    # just says "this page has been blocked by chrome" inside
    if st.button("Show PDF in iframe with highlights, via regular url w/ encoded params"):
        components.iframe(FULL_DOC_URL, height=800, scrolling=True)
    

if __name__ == "__main__":
    main()

…and the latest (unzipped) release/code of pdf.js vendor’d into my repo under vendor/ like so:


|streamlit_n_pdfjs
|--app.py
|--vendor
|----pdfjs-4-9-155-dist
|------web
|--------viewer.html

Finally, I have a .streamlit/config.toml with:

[server]
enableStaticServing = true

and I launch my streamlit app locally with:

PYTHONPATH=. streamlit run app.py

What I get is documented in the code above, but for clarity there’s three links/buttons shown in the app (none of which work to my requirements):
enter image description here

  1. A link which opens the pdf file in a new tab to a given page, but does not use an iframe (deliberately, this is mainly testing the pdf/doc is available at the url), and not with the desired text/quote highlighted successfully
  2. A button that attempts to launch an iframe with the document using pdf.js, but just triggers a 404 message
  3. A button that attempts to launch an iframe with the document just via default browser pdf rendering from the url, but which triggers a message “this page has been blocked by Chrome”