How to pass React state variable into setInterval function - javascript

I created a drag and drop menu and so far everything works, however, I have run into a small coding problem for my last step. I need to get the menu of items to automatically scroll when the dragged item is towards the bottom of the container (or top). I decided to structure it so that when the cursor dragging the item falls below a specific range towards the bottom, a scroll function will trigger and continually scroll down at a pace I can set. In order to achieve this I set up a setInterval function and will continually call a scrollBy function.
My issue is that In order to stop the setInterval and call clearInterval, I need to know when the position of the cursor is no longer in that range. Every time the function is called in setInterval, it takes a snapshot of the current state variables. Instead of updating the state of those variables (yAxis position) within the function, it is stuck on the first value it gets.
The yPoisiton is obtained with the window.event.clientY from another component.
document.onmousemove = (e) => {
if (dragging.state) {
var newYPos = window.event.clientY;
setYPos(newYPos);
handleScroll(newYPos, dragging.state);
The rest of the code goes as follows:
const menuContainer = useRef();
const [scrolling, setScrolling] = useState(false);
const [yPos, setYPos] = useState(null);
const [dragging, setDragging] = useState({
state: false,
index: null,
y: null,
height: null,
});
function continueScrolling() {
console.log("continueScrolling function");
console.log(yPos); // Same value even after updating via setYPos)
const date = new Date();
console.log(date.toLocaleTimeString()); //Different values in accordance to the time
}
console.log("Index.js yPos");
console.log(yPos); // Different values every render when dragging
const handleScroll = (draggingYPos, isDragging) => { // This is triggered onMouseMove in another component
if (
draggingYPos >
menuContainer.current.getBoundingClientRect().bottom - 100 &&
isDragging
) {
setScrolling(true); // On the first cycle scrolling == false and executes the setInterval function. Each subsequent call is then skipped.
if (scrolling) {
return;
} else {
var intr = setInterval(function () {
continueScrolling(); // Different method of testing and trying to get the current yPos in the function
//Commented but shows a bit of what I'm trying to accomplish
// if (
// !(
// yPos >
// menuContainer.current.getBoundingClientRect().bottom - 100 &&
// dragging.state
// )
// )
// clearInterval(intr);
}, 1000);
Please let me know if there is more information you need or anything else I can clarify.

This is a temporary solution I found to my problem, however, I would still like to know a more proper and robust solution if anyone has one, as well as information as to the scope of functions, React state, and how to pass them between one another.
I realize that it is not recommended to mutate states directly, however, this is my only current working solution. If I create a temporary value and pass in the setInterval, it will call setInterval a second time and will still be active after clearing it.
If I declare a global const variable, I cannot reassign the variable with setInterval. When I declared a global variable with var, the setInterval function continued even after I cleared it.
Adjusted code:
const [scroll, setScroll] = useState(null);
const handleScroll = (draggingYPos, isDragging) => {
if (
draggingYPos >
menuContainer.current.getBoundingClientRect().bottom - 100 &&
isDragging
) {
setScrolling(true);
if (scrolling) {
return;
} else {
var count = 0;
if (
!(
yPos > menuContainer.current.getBoundingClientRect().bottom - 100 &&
dragging.state
)
) {
setScroll(
setInterval(function () {
count += 1;
console.log(count);
}, 1000)
);
}
}
} else {
console.log("Not Scrolling");
setScroll(clearInterval(scroll));
setScrolling(false);
}
};

Related

a function that supposed to return a different value each time does not work in react

I am working on my portfolio, but when I reach the experiences page I tried to create a title that has it's middle part change every second, and value of it must come from an already set up array,
but when I run the code it always return the first string of the array,
can anyone please fix this problem for me ?
const projectsTitleKeyWords = ['responsible', 'meaningful', 'beautiful']
let titlep2 = 'test'
let index = 0
const change = () => {
titlep2 = projectsTitleKeyWords[index]
index = ++index % projectsTitleKeyWords.length
setTimeout(change, 1000)
}
change()
console.log(titlep2)
const titlep1 = 'I creat '
const titlep1Array = titlep1.split('')
let titlep2Array = titlep2.split('')
const titlep3 = ' projects'
const titlep3Array = titlep3.split('')
the value of titlep2Array will be received by
<AnimatedLetters
letterClass={letterClass}
strArray={titlep2Array}
idx={15}
id='to-change'
/>
In-order to reflect UI changes in React, a component must re-render.
A React component re-renders in 1 of 2 scenarios:
Whenever there's a change in the value of a local state.
Whenever any of it's parent components re-render.
Therefor, since changes in the UI are only reflected upon a re-render, we should manage a local state that would be responsible for this behavior.
With functional components in React, this can be achieved via the useState hook.
In your case, we can simply make titlep2 a state, instead of a regular variable.
Example:
const [titlep2, setTitlep2] = useState('')
const change = () => {
setTitlep2(projectsTitleKeyWords[index])
index = ++index % projectsTitleKeyWords.length
setTimeout(change, 1000)
}
<AnimatedLetters
letterClass={letterClass}
strArray={titlep2.split('')}
idx={15}
id='to-change'
/>
Note: since this function now updates the state, we can't call it the way you did in your example, since it will run every time the component re-renders, making the component re-render indefinitely due to the change in state.
Therefor, we can use the useEffect hook in-order to allow it to run only once on the initial render.
Example:
const change = () => {
setTitlep2(projectsTitleKeyWords[index])
index = ++index % projectsTitleKeyWords.length
setTimeout(change, 1000)
}
useEffect(() => {
change()
}, [])
Furthermore, if there are any other variables that should reflect changes in the UI, they can be convert to states as well.
for that try using the setInterval() instead of setTimeout().
You are trying to make the text change after a specific interval in this case 1 second.
You should also consider doing that will CSS animations, it seems this is overkill.
const change = () => {
titlep2 = projectsTitleKeyWords[index]
index = ++index % projectsTitleKeyWords.length
console.log(titlep2)
setInterval(change, 1000)
}
change()

'Calling' UseEffect from another 'UseEffect'

I'm learning react native and I'm programing a simple app to register the time of sleep of each day.
When the button that add the new register is pressed I do this
onPress={() => setUpdateAction(true)}
That changes the value of the updateAction:
const [updateAction, setUpdateAction] = useState(false);
When the value of updateAction is changed this will be executed:
useEffect(() => {
... code that add's the register to an array
setviewInfoAction(true);
setUpdateAction(false);
}, [updateAction]);
And inside I call setviewInfoAction(true); becouse I want to change the value that is showed with the value that was inserted.
const [viewInfoAction, setviewInfoAction] = useState(false);
useEffect(() => {
console.log("CALLED");
var seted = false;
for (var i = 0; i < registSleep.length; i++) {
if (
registSleep[i].day === selectedDay &&
registSleep[i].month === selectedMonth &&
registSleep[i].year === selectedYear
) {
setSelectedDayHours(registSleep[i].hours);
seted = true;
}
}
if (!seted) {
setSelectedDayHours(0);
}
setviewInfoAction(false);
}, [viewInfoAction]);
Doing this I was expecting for the second UseEffect to executed but it's not...
The way you have your useEffect set up it will only ever re-run if selectedDay changes. If you want to make it run when setInfoViewAction is executed add viewInfoAction into the dependencies.
Even better because all of this is related logic to the same sideEffect. I would simplify your code by removing the second useEffect and adding the code inside it into the first useEffect. This is mainly just because you can keep all related side effect logic together.

Issue accessing state array from inside animationFrame with react

I am trying to update some elements on scroll by using animationFrame. I want to add an easing effect so I would like the elements to update their positions by the eased value. I figured the best way to do this would be to store all of the values in an array and update them accordingly. When each element is mounted I am sending them to a context element that adds them to the state value array.
My issue is that I cannot access the array from inside the animating function. It is available outside of the animating function but not inside. I am assuming that the animation is starting before the array is being populated but I have tried to stop and restart the animation when the blocks array changes with useEffect but to no avail.
Here is a codesandbox of the issue Example Of Issue
In the sandbox you can see in the animate() function in the ScrollContainer component I am console logging the blocks array and then after the function I am logging the same array. When you scroll the array does not log the available blocks only an empty array. But the available blocks are being logged correctly under this function.
const animate = () => {
const diff = yScroll - yCurrent;
const delta = Math.abs(diff) < 0.1 ? 0 : diff * ease;
if (delta) {
yCurrent += delta;
yCurrent = parseFloat(yCurrent.toFixed(2));
animationFrame = requestAnimationFrame(animate);
} else {
cancelAnimation();
}
console.log("Animating Blocks", blocks);
};
console.log("Available Blocks", blocks);
const addBlock = block => {
setBlocks(prev => {
return [...prev, block];
});
};
and here is how I am starting the animation
const startAnimation = () => {
if (!animationFrame) {
animationFrame = requestAnimationFrame(animate);
}
};
useEffect(() => startAnimation(), []);
Thanks.
I played with your example. It seems to me, that the problem is in the useEffect. If you add empty dependency to it, then it runs only once after the first render. There will be a second render when blocks state updates, but for the useEffect only the first state is visible because it runs only once and it uses the startAnimation with stale closure. This version of startAnimation uses the first version of the animation with the original state.
Your initial problem is solved if you add blocks to the useEffect as a dependency.
useEffect(() => {
yScroll = window.scrollY || window.pageYOffset;
yCurrent = yScroll;
startAnimation();
window.addEventListener("scroll", updateScroll);
return () => {
window.removeEventListener("scroll", updateScroll);
};
}, [blocks]);
I tried adding the animation, but is is quite choppy to me. I'm interested in your final solution. This is mime: https://codesandbox.io/s/scrolling-animation-frame-array-issue-d05wz
I use higher level animation libraries like react-spring. You can consider using something like this. I think it is much easier to use.
https://codesandbox.io/s/staging-water-sykyj

Calling function from bound function always gives me the same value?

I'm sure there's a really simple solution to this, but I can't figure it out.
import Mousetrap from 'mousetrap';
const Slider = ({ children }) => {
const [activeSlide, setActiveSlide] = useState(0);
useEffect(() => {
console.log('mounted');
const element = document.querySelector('#trap');
let trap = new Mousetrap(element);
trap.bind('left', () => {
test();
});
}, []);
const test = () => {
console.log(activeSlide + 1);
};
}
No matter what slide I am on, whenever I call trap.bind('left', ... it thinks I am on slide number 0 and want to go to slide number 1. I guess it checks the value of activeSlide at build time, and then keeps that value, so it doesn't get updated since my function is bound? How can I make sure it always knows the currently active slide?

How to trigger a var after x actions

I have the following code. I want to trigger the action in function activityDetected(eventName) only after 100 click. How to do this ?
I know I have to put let a = 1; ++a but not sure where...
https://pastebin.com/SMsJsikE
const intervalTimeout = 2000;
//here is where code should be added. let a = 1; ++a...
function activityDetected(eventName) {
console.log(`Activity detected with the event name: ${eventName}!`);
clearInterval(activityTimeout);
activityTimeout = setInterval(recordNoActivity, intervalTimeout);
}
document.addEventListener('click', _ => {
activityDetected('click');
});
You need to declare a counter outside the function and up it by 1 when the eventName is 'click'. After that check for a % 100 and put whatever action you want to call every 100 clicks in there.
Look at the code example:
// For ease, change this to a smaller value to detect more often, higher to detect less often!
const intervalTimeout = 2000;
let a = 0;
// Here's our interval, setting up the initial capture of no activity
let activityTimeout = setInterval(recordNoActivity, intervalTimeout);
// A single function to handle the events we're listening to.
// clears the interval and restarts it, also tells us which event has cleared the interval!
//here is where code should be added. let a = 1; ++a...
function activityDetected(eventName) {
if(eventName == 'click'){
a++;
if(a%100 == 0){
// Trigger whatever you want to trigger after every 100 clicks
}
}
console.log(`Activity detected with the event name: ${eventName}!`);
clearInterval(activityTimeout);
activityTimeout = setInterval(recordNoActivity, intervalTimeout);
}
// Set listening events
document.addEventListener('keydown', _ => {
activityDetected('keydown');
});
document.addEventListener('click', _ => {
activityDetected('click');
});
As correctly pointed by others on this thread the variable a should be declared and defined outside the function but the reason why this approach would work is because of Closure
So when the function is getting invoked an execution context is created which contains
scopeChain - it contains variableObject + all parent execution context's variableObject
variableObject - it contains function arguments / parameters, inner variable and function declarations
this - the this context.
Thus the variable a values would be saved before invoking the function and the variable will keep incrementing.

Categories

Resources