Throttle with lodash isn't throttling using ReactJS - reactjs

So I have this code
import React, { createRef, useEffect, useCallback, useState } from 'react';
import { throttle } from 'lodash';
import { setProgress } from '../../helpers/markersApi';
const EXECUTE_EVERY_THIRTY_SECONDS = 30 * 1000;
const throttledSetProgress = throttle(setProgress, EXECUTE_EVERY_THIRTY_SECONDS);
const Player = ({}) => {
const updateProgress = (playerPosition, asset, immediateUpdate = false) => {
if (asset.type !== 'EPG_PROGRAM') {
const {
id, episode,
} = asset;
const type = (episode && episode.episodeNumber) ? 'episode' : 'movie';
if (immediateUpdate) {
console.log('IMMEDIATE');
// Cancel possible future invocations and set progress immediately
throttledSetProgress.cancel();
setProgress(id, playerPosition, type);
} else {
throttledSetProgress(id, playerPosition, type);
}
}
};
useEffect(() => {
updateProgress(position, playerAsset);
}, [position, playerAsset]);
}
Problem is the the throttling isn't working since it's running the setProgress every time useEffect is called. Any ideas?

The throttled function should remain the same between re-renderings, meaning we have to use React's UseCallback function. This works by changing throttledSetProgress from this:
const throttledSetProgress = throttle(setProgress, EXECUTE_EVERY_THIRTY_SECONDS);
To this:
const throttledSetProgress = useCallback(
throttle(setProgress, EXECUTE_EVERY_THIRTY_SECONDS),
[],
);
(Don't forget to import useCallback from 'react' as well)

Related

How can I detect if two elements are colliding in React?

So, I created a custom hook to detect if my Navbar collides with a specific div in my Portfolio, but I'm a bit insecure if this hook is performance friendly and if it has good practices. Is it okay to use this kind of Event Listener with a useEffect ?
import { useEffect, useState } from "react";
export const useCheckNavCollision = (
navRef: React.RefObject<HTMLElement>,
collisionRef: React.RefObject<HTMLElement>,
scrollListener: React.RefObject<HTMLElement>
) => {
const [detected, setDetected] = useState<boolean>(false);
useEffect(() => {
const scroll = () => {
const greenContainer = collisionRef.current!.getBoundingClientRect().bottom,
navHeight = navRef.current!.getBoundingClientRect().height;
if (navHeight > greenContainer) setDetected(true);
else setDetected(false);
};
if (scrollListener) {
scrollListener.current!.addEventListener("scroll", scroll);
return () => scrollListener.current!.removeEventListener("scroll", scroll);
}
}, [navRef, collisionRef, scrollListener]);
return detected;
};

Why is there a delay when I do useState and useEffect to update a variable?

I saw quite a few posts about a delay between setting the state for a component, however, I'm running into this issue in a custom hook that I built. Basically, the classNames that I'm returning are being applied, just with a delay after the component first renders. I've tried using callback functions and useEffect with no luck. Does anyone have any ideas about why there is a small delay?
import * as React from 'react';
import classNames from 'classnames';
import debounce from 'lodash/debounce';
const useScrollStyling = ref => {
const [isScrolledToBottom, setIsScrolledToBottom] = React.useState(false);
const [isScrolledToTop, setIsScrolledToTop] = React.useState(true);
const [isOverflowScrollingEnabled, setIsOverflowScrollingEnabled] = React.useState(false);
const { current } = ref;
React.useEffect(() => {
if (current) {
const { clientHeight, scrollHeight } = current;
setIsOverflowScrollingEnabled(scrollHeight > clientHeight);
}
}, [current]);
const handleScroll = ({ target }) => {
const { scrollHeight, scrollTop, clientHeight } = target;
const isScrolledBottom = scrollHeight - Math.ceil(scrollTop) === clientHeight;
const isScrolledTop = scrollTop === 0;
setIsScrolledToBottom(isScrolledBottom);
setIsScrolledToTop(isScrolledTop);
};
return {
handleScroll: React.useMemo(() => debounce(handleScroll, 100), []),
scrollShadowClasses: classNames({
'is-scrolled-top': isOverflowScrollingEnabled && isScrolledToTop,
'is-scrolled-bottom': isScrolledToBottom,
'is-scrolled': !isScrolledToTop && !isScrolledToBottom,
}),
};
};
export default useScrollStyling;

useEffect is being called twice even if it state is changed once

i made login hook called "useMakeQueryString". which is responsble for making queryString from Object.
import { useEffect, useState } from "react";
export default function useMakeQueryString(obj) {
const [queryString, setQueryString] = useState("");
const makeQueryString=(obj)=> {
let queryString1 = "";
for (let key in obj) {
//if (key instanceof Object) queryString1 += makeQueryString(obj);
queryString1 += key + "=" + obj[key] + "&";
}
setQueryString(queryString1.slice(0, queryString1.length - 1));
}
useEffect(()=>{
makeQueryString(obj)
},[obj])
return { queryString, makeQueryString };
}
then i imported that hook to my Google Component. on click of Component it calls the performAuth function and that function set the option state. and useEffect on option change is called. inside useEffect which is being called on option change i try to change queryString State. but the problem is useEffect on queryString change is being Called Twice
import useMakeQueryString from "../Login/LoginHook";
import { useEffect,useState } from "react";
export default function Google() {
const g_url = "https://accounts.google.com/o/oauth2/v2/auth";
const {queryString,makeQueryString} = useMakeQueryString({});
let [option,setOption] = useState({})
useEffect(() => {
console.log("length"+Object.keys(option).length)
if(Object.keys(option).length!=0) {
makeQueryString(option); // setQueryString(query);
}
}, [option])
useEffect(()=>{
if(queryString)
window.location = `${g_url}?${queryString}`;
},[queryString])
const performAuth = () => {
console.log("perform cliked")
const option1 = {
client_id: "432801522480-h02v02ivvti9emkd019fvreoisgj3umu.apps.googleusercontent.com",
redirect_uri: "http://localhost:3000/glogin",
response_type: "token",
scope: [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
].join(" "),
}
setOption(option1);
}
return (
<>
<button className="google-btn social-btn" onClick={() => performAuth()}>SignUp With Google</button>
</>
)
}

unable to show remaining time in wavesurfer.js

import React,{ useEffect, useRef, useState } from "react";
import { useDispatch, useSelector } from 'react-redux'
import { format, formWaveSurferOptions } from '../../utils/generic'
import { receiveCurrentTrack, receiveTrackRef , togglePlay} from '../../redux/action/currentTrack'
import './_playlist.scss'
import WaveSurfer from "wavesurfer.js";
import {Link} from 'react-router-dom';
const PlayList = (props) => {
const dispatch = useDispatch()
const { track, queue, currentTrack, index } = props
const waveformRef = useRef(null);
const waveRef = useSelector((state)=>state.currentTrack.waveRef)
const wavesurfer = useRef(null);
const currentTime = useRef(null)
const [duration, setDuration] = useState()
useEffect(() => {
const options = formWaveSurferOptions(waveformRef.current);
wavesurfer.current = WaveSurfer.create(options);
wavesurfer.current.load(track.url);
wavesurfer.current.on("ready", ()=> {
if(wavesurfer.current){
wavesurfer.current.setVolume(0)
setDuration(format(wavesurfer.current.getDuration()))
}
});
return () => wavesurfer.current.destroy();
}, [track.url]);
useEffect(()=>{
if(currentTrack){
dispatch(receiveTrackRef(wavesurfer))
}
return ()=> waveRef?.current.stop()
},[currentTrack.track?.url])
const handleClick = () => {
dispatch(receiveTrackRef(wavesurfer))
if(currentTrack){
dispatch(togglePlay())
}
else {
waveRef?.current.stop()
dispatch(receiveCurrentTrack(track, queue));
}
};
return (
<div
className={ currentTrack ? "playlist-item selected playlist" : "playlist-item playlist"}
onClick={handleClick}
>
<div># {index} :</div>
<Link to='/track'>{track.title}</Link>
<div>{track.artist}</div>
<div><span>0.00</span> / {duration && duration}</div>
<div className="wave-form" id="waveform" onChange={()=>console.log('hhh')} ref={waveformRef} />
<div>{duration && duration}</div>
</div>
);
};
export default PlayList;
React-Wavesurfer was unmaintained, so i have moved on to the wavesurfer.js but the issue arrises the now how i can detect the audio current time and show to the page e.g. 0:01 / 3.02, i have use wavesurfer.on('audioprocess') but i can not detect it, so if any one knows the solution please help, thankyou :)
From the Official Doc :
getCurrentTime() – Returns current progress in seconds.
There's also a audioprocess event as you mentioned that fires continuously as the audio plays.
So by combining these together, we have this:
let current;
wavesurfer.on('audioprocess', () => {
current = wavesurfer.getCurrentTime();
});
Note that your wavesurfer player is assigned to the name wavesurfer.current. so the code should be wavesurfer.current.on('audioprocess', () => {});

How to get value from useState inside the function

I am trying to build Hanging man game and want to get value from useState inside the checkMatchLetter function, but not sure if that is possible and what I did wrong....
import React, { useState, useEffect } from 'react';
import { fetchButton } from '../actions';
import axios from 'axios';
import 'babel-polyfill';
const App = () => {
const [word, setWord] = useState([]);
const [underscore, setUnderscore] = useState([]);
const [data, setData] = useState([]);
useEffect(() => {
const runEffect = async () => {
const result = await axios('src/api/api.js');
setData(result.data)
}
runEffect();
}, []);
const randomWord = () => {
const chosenWord = data[Math.floor(Math.random() * data.length)];
replaceLetter(chosenWord.word);
}
const replaceLetter = (string) => {
let getString = string; // here it shows a valid string.
setWord(getString);
let stringToUnderScore = getString.replace(/[a-z]/gi, '_');
setUnderscore(stringToUnderScore);
}
useEffect(() => {
const checkLetter = (event) => {
if(event.keyCode >= 65 && event.keyCode <= 90) {
checkMatchLetter(word, String.fromCharCode(event.keyCode).toLowerCase());
}
};
document.addEventListener('keydown', checkLetter);
return () => {
document.removeEventListener('keydown', checkLetter);
}
}, []);
const checkMatchLetter = (keyButton) => {
console.log(keyButton);
let wordLength = word.length;
console.log(wordLength); // here it outputs '0'
/// here I want word of useState here....
}
return (
<div>
<p>{word}</p>
<p>{underscore}</p>
<button onClick={randomWord}></button>
</div>
)
}
export default App;
The reason why I want to obtain that value inside this function is so I can compare the clicked keybutton (a-z) to the current chosenword. And if there is something wrong with other functions, please feel free to share your feedback here below as well.
You're using a variable defined inside the component render function in a useEffect effect and that variable is missing in the hook's deps. Always include the deps you need (I highly recommend the lint rule react-hooks/exhaustive-deps). When you add checkMatchLetter to deps you'll always have the newest instance of the function inside your effect instead of always using the old version from the first render like you do now.
useEffect(() => {
const checkLetter = (event) => {
if(event.keyCode >= 65 && event.keyCode <= 90) {
checkMatchLetter(word, String.fromCharCode(event.keyCode).toLowerCase());
}
};
document.addEventListener('keydown', checkLetter);
return () => {
document.removeEventListener('keydown', checkLetter);
}
}, [checkMatchLetter, word]);
This change will make the effect run on every render. To rectify that, you can memoise your callbacks. However, that's a new can of worms.

Resources