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

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?

Related

Detecting a prop value change

I am very new to react. I am currently creating a game and trying to detect if the current turn has changed. I am wondering if there is a simple way to do this.
let hasTurnChanged = props.turn % 2 == 1;
function chooseBestPrice() {
// start by seeing if prices fluctuate every turn
let currBestPrice = props.price;
console.log(hasTurnChanged);
if(hasTurnChanged){
currBestPrice = fluctuatePrice(props.price);
}
return currBestPrice;
}
When I click a button called Turn the prices are suppose to change.
Assuming you're trying to detect a prop come from parent component, useEffect could help with this.
All we need to do is put the prop into the dependencies array of useEffect.
const ChildComponent = (props) => {
useEffect(() => {
// call the function here
}, [props.price])
// ...other code
}
See the official document for more information.

How to pass React state variable into setInterval function

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);
}
};

React useEffect in function

I've been trying to find a way to use useEffect like this, but I don't think I'm doing it properly:
const [searching, setSearching] = useState(false);
function OnSearch(e) {
const searchValue = e.target.value;
useEffect(() => {
setSearching(true);
}, []);
clearTimeout(timer);
timer = setTimeout(() => {
setSearching(false);
window.location.href = '/block/' + searchValue;
}, 2000);
}
Any kind of help or direction will be appreciated.
I assume from the useState you've used there that this code is inside a functional component. You don't need or want useEffect there, just remove it and do setSearching(true) directly:
const [searching, setSearching] = useState(false);
function OnSearch(e) {
const searchValue = e.target.value;
setSearching(true);
clearTimeout(timer);
timer = setTimeout(() => {
setSearching(false);
window.location.href = '/block/' + searchValue;
}, 2000);
}
(In fact, not only do you not need it, but you can't use hooks anywhere other than the top level of a component function or hook function. So not in an event handler.)
That leaves the question of the timer variable. It can't just be a local variable in the component function because that will get recreated on every render. For non-state instance data like that, you can use an object via useRef, then use properties on that object, which will be consistent for the lifetime of the component:
const [instance] = useRef({});
So that gives us:
const [instance] = useRef({});
const [searching, setSearching] = useState(false);
function OnSearch(e) {
const searchValue = e.target.value;
setSearching(true);
clearTimeout(instance.timer);
instance.timer = setTimeout(() => {
setSearching(false);
window.location.href = '/block/' + searchValue;
}, 2000);
}
Side note: In JavaScript, the overwhelmingly-common convention is that only constructor functions and (in React) component functions are capitalized, not other kinds of functions. So onSearch rather than OnSearch. You can do what you like in your own code of course, but when working with others or asking for help, sticking to conventions helps keep communication clear.

'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.

How can I convert scrollIntoView with smooth animation to a Promise?

I have to scrollIntoView a particular element smoothly and then do something.
Example
element.scrollIntoView({behavior: 'smooth'}).then(() => {
// Do something here
})
I know that it can't be done this way as native scrollIntoView doesn't return a Promise. But, how do I achieve something like this?
I'm using Angular 7 BTW. So if there are any directives that could help me achieve this, it would be great.
You can work with prototypes, I think this could be a solution to your problem without download any npm packages
/* Extends Element Objects with a function named scrollIntoViewPromise
* options: the normal scrollIntoView options without any changes
*/
Element.prototype.scrollIntoViewPromise = function(options){
// "this" refers to the current element (el.scrollIntoViewPromise(options): this = el)
this.scrollIntoView(options);
// I create a variable that can be read inside the returned object ({ then: f() }) to expose the current element
let parent = this;
// I return an object with just a property inside called then
// then contains a function which accept a function as parameter that will be execute when the scroll ends
return {
then: function(x){
// Check out https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API for more informations
const intersectionObserver = new IntersectionObserver((entries) => {
let [entry] = entries;
// When the scroll ends (when our element is inside the screen)
if (entry.isIntersecting) {
// Execute the function into then parameter and stop observing the html element
setTimeout(() => {x(); intersectionObserver.unobserve(parent)}, 100)
}
});
// I start to observe the element where I scrolled
intersectionObserver.observe(parent);
}
};
}
element.scrollIntoViewPromise({behavior: "smooth"}).then(()=>console.log("EHI!"));
I've created an example. I know it's not an angular application, but it's a good starting point. You just need to implement it (If you're using typescript you have to create an interface which extends Element with the new function).
One way you can solve this is by using smooth-scroll-into-view-if-nedded it actually return a promise so you can bind to it and apply your logic.
There is an idea how you may catch animation ending.
You may do it in vanilla JS with a 'scroll' event listener.
Check this example https://codepen.io/Floky87/pen/BEOYvN
var hiddenElement = document.getElementById("box");
var btn = document.querySelector(".btn");
var isScrolling;
function handleScroll(event) {
// Clear our timeout throughout the scroll
window.clearTimeout(isScrolling);
// Set a timeout to run after scrolling ends
isScrolling = setTimeout(function() {
alert(1);
document.removeEventListener("scroll", handleScroll);
}, 66);
}
function handleButtonClick() {
document.addEventListener("scroll", handleScroll, false);
hiddenElement.scrollIntoView({ block: "center", behavior: "smooth" });
}
btn.addEventListener("click", handleButtonClick);
I made it like this
const scrollIntoViewPromise = async (node: HTMLElement, options?: ScrollIntoViewOptions) => {
node.scrollIntoView(options);
return new Promise((resolve) => {
const intersectionObserver = new IntersectionObserver((entries) => {
const [entry] = entries;
if (entry.isIntersecting) {
setTimeout(() => {
resolve(true);
intersectionObserver.unobserve(node);
}, 100);
}
});
intersectionObserver.observe(node);
});
};

Categories

Resources