I'm currently having an issue with the function that I've created. Ideally it would retrieve the window.document.body.offsetHeight as soon as it's active, however the function won't work as intended if I don't start with window.innerHeight. I would like the entire document height, not the viewport height, hence the use of window.document.body.offsetHeight. However the function outright doesn't work if I use window.document.body.offsetHeight in the first constant statement.
function useWindowSize() {
const [size, setSize] = useState([window.innerHeight, window.innerWidth]);
useEffect(() => {
let timeoutId = null;
const resizeListener = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => setSize([window.document.body.offsetHeight, window.document.body.offsetWidth]), 150);
};
window.addEventListener("resize", resizeListener);
return () => {
window.removeEventListener("resize", resizeListener)
};
})
return size;
}
I'd like to make it so that the initial value of size uses the window.document.body.offsetHeight, however I can't determine why my function doesn't seem to accept anything but window.innerHeight initially. The strange thing is that as soon as I resize it, the second part of the function works and the window.document.body.offsetHeight is returned.
Why don't you call the resize event manually inside the useEffect (which runs after the render, so the document layout should have been set) ?
function useWindowSize() {
const [size, setSize] = useState([window.innerHeight, window.innerWidth]);
useEffect(() => {
let timeoutId = null;
const resizeListener = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => setSize([window.document.body.offsetHeight, window.document.body.offsetWidth]), 150);
};
window.addEventListener("resize", resizeListener);
resizeListener(); // manually call the resize handler once to set it up
return () => {
window.removeEventListener("resize", resizeListener)
};
}, []) // add an empty dependency array so that it only re-runs on unmount
return size;
}
The problem is that your useWindowSize hook is run before the client has actually rendered the page. The simplest solution is to call resizeListener once in the useEffect function, since useEffect runs after the page has been rendered.
function getWindowSize() {
return [window.document.body.offsetHeight, window.document.body.offsetWidth];
}
function useWindowSize() {
const [size, setSize] = useState(getWindowSize());
useEffect(() => {
let timeoutId = null;
const resizeListener = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => setSize(getWindowSize()), 150);
};
resizeListener(); // this will correctly set the size after the page has been rendered.
window.addEventListener("resize", resizeListener);
return () => {
window.removeEventListener("resize", resizeListener)
};
})
return size;
}
This will set the width and height initially to 0, and then to the correct value the next frame.
Related
I have a state that tracks the window width:
const [innerWidth, setInnerWidth] = useState(window.innerWidth)
In useEffect, I create a resize eventListener which sets the state to the new width:
useEffect(() => {
document.addEventListener('resize', () => {
setInnerWidth(window.innerWidth)
})
}, [])
Lastly, I have a function test that logs the innerWidth every 5 seconds, with an interval started in useEffect
function test() {
console.log(innerWidth)
}
useEffect(() => {
setInterval(test, 5000)
}, [])
Unfortunately, despite any resize that happen, the test() function keeps on logging the original innerWidth value.
How can I tell react to reload the test function as well?
EDIT:
The perpetual log of the innerWidth was just a simplification of my actual use case. Actually, the timer is shifting an element on the x-axis, and I need to know when it exceeds the width to stop the execution and start again.
Creating and invalidating a loop every time the window changes, like in several answers you've given, temporarily stops the shifting of my element, as the loop gets invalidated. I would like to avoid this.
The useEffect created a closure around the original values, so that's all it ever logs. You'd need the effect to update any time the value changes, by adding it to the dependency array:
useEffect(() => {
setInterval(test, 5000)
}, [innerWidth])
This would of course create a new interval on every state change. So the useEffect should return a function which cancels the interval:
useEffect(() => {
const x = setInterval(test, 5000);
return () => clearInterval(x);
}, [innerWidth])
That way there's only one interval running at any given time.
Though this begs the question... Why? If the goal is to log the value of innerWidth to observe its changes, then why re-log the same value every 5 seconds indefinitely? Skip the test function and the interval entirely and just log the value any time it changes:
useEffect(() => {
console.log(innerWidth);
}, [innerWidth])
Can you change the test function to an anonymous function?
const test = () => {
console.log(innerWidth);
};
Change you useEffect:
useEffect(() => {
window.addEventListener('resize', () => {
setInnerWidth(window.innerWidth);
});
}, [setInnerWidth]);
The solution was wrapping the innerWidth into an object, so that it is passed by reference and it 'updates' in the test function.
const innerWidthWrapper = {width: window.innerWidth}
useEffect(() => {
innerWidthWrapper.width = window.innerWidth
})
}, [])
Edit: Your issue using the interval function is explained in this answer
This code works for me by logging the state variable using the effect hook:
const [innerWidth, setInnerWidth] = useState(window.innerWidth);
useEffect(() => {
const updateWidth = () => {
setInnerWidth(window.innerWidth);
};
window.addEventListener("resize", updateWidth);
}, []);
useEffect(() => {
console.log(innerWidth);
}, [innerWidth]);
I added the eventlistener using the window object.
Sandbox
Hello I would like to put setInterval in my React project to add 1 for each second but I got an error like in title of this post.
js:
const [activeTab, setActiveTab] = useState(0)
useEffect(() => {
setInterval(setActiveTab(prevTab => {
if (prevTab === 3) return 0
console.log('hi')
return prevTab += 1
}), 1000)
})
There are a few issues:
You're not passing a function to setInterval, you're calling setActiveTab and passing its return value into setInterval.
If you were passing in a function, you'd be adding a new repeated timer every time your componennt ran. See the documentation — useEffect:
By default, effects run after every completed render...
And setInterval:
The setInterval() method... repeatedly calls a function or executes a code snippet, with a fixed time delay between each call.
(my emphasis)
Starting a new repeating timer every time your component re-renders creates a lot of repeating timers.
Your component will leave the interval timer running if the component is unmounted. It should stop the interval timer.
To fix it, pass in a function, add a dependency array, and add a cleanup callback to stop the interval:
const [activeTab, setActiveTab] = useState(0);
useEffect(() => {
const handle = setInterval(() => { // *** A function
setActiveTab(prevTab => {
if (prevTab === 3) return 0;
console.log("?ghi");
return prevTab += 1;
});
}, 1000);
return () => { // *** Clear the interval on unmount
clearInterval(handle); // ***
}; // ***
}, []); // *** Empty dependency array = only run on mount
Side note: Assuming you don't need the console.log, that state setter callback function can be simpler by using the remainder operator:
setActiveTab(prevTab => (prevTab + 1) % 3);
useEffect(() => {
setInterval(() => {
setActiveTab((prevTab) => {
if (prevTab !== 3) {
return (prevTab += 1);
} else {
return 0;
} // console.log("hi");
});
}, 1000);
}, []);
I have two onclick functions that performing start and stop functions using socket when i click start it start requesting images from server(i am using intervals) when i click stop the server is stopped but the request keep coming to the server. the intervals seems not stopped after clicking kindly review my code
Code:
var interval;
function onclick() {
interval = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350)
}
function onclicks() {
clearInterval(interval);
setImage(blanked);
}
I have tried to add clearInterval function but it seems not working
Variables declared at the top level of a component are redeclared on each render (here every time you call setImage()) so interval is undefined by the time onclicks() is called.
In order to maintain the value through renders you need to either use state (useState) or a ref (useRef) which won't lose it's value. In this case, since you don't want renders attached to the value of interval a ref is more appropriate. useRef() returns an object with a current property in which your value will be stored.
const interval = React.useRef(null);
function onclick() {
interval.current = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350);
}
function onclicks() {
clearInterval(interval.current);
interval.current = null; // reset the ref
setImage(blanked);
}
You probably also want to avoid setting multiple intervals. You may already be doing so, but if not you can check if interval is null or not before setting a new one.
const interval = React.useRef(null);
function onclick() {
if (interval.current === null) { // only set new interval if interval ref is null
interval.current = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350);
}
}
For me, the easiest way to solve this behavior was set the interval as a Ref:
import { useRef } from 'react';
// Initialize the interval ref
const interval = useRef();
// Create the interval
function onclick() {
interval.current = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350)
}
// Remove the interval
function onclicks() {
clearInterval(interval.current);
setImage(blanked);
}
Using this hook, the component will not re-render when you change the value remove/change the interval.
Let's say there is a code in place 2
var place2IsReady = true;
In place 1 I need to implement the logic below :
Once place2IsReady value was changed (to true) then display alert('ready!');
Notes:
place2IsReady variable is not available in the scope of place 1.
the code from place 1 gets executed before place 2 gets executed (or there is a race condition).
Solution 1
I believe I can use window.place2IsReady instead and use setTimeout/setInterval in place 1 until I get window.place2IsReady === true.
Any better options? Using Listeners? On the variable change?
P.S. I need to track only first possible change of place2IsReady.
Is there a better way? Thank you.
You can create a listener for the variable change using setTimeout, something like:
let place2IsReady = false;
setReadyListener();
// testing wait 2 seconds to set place2IsReady to true
// so: an alert should occur after 2 seconds
setTimeout(() => place2IsReady = true, 2000);
function setReadyListener() {
const readyListener = () => {
if (place2IsReady) {
return alert("Ready!");
}
return setTimeout(readyListener, 250);
};
readyListener();
}
A more generic listener 'factory' could be:
let place2IsReady = false;
let fromObj = {
place2IsReady: "busy",
done() { this.place2IsReady = "done"; },
};
const listen = changeListenerFactory();
listen(
() => place2IsReady,
() => console.log("place2IsReady") );
listen(
() => fromObj.place2IsReady === "done",
() => console.log("formObj.place2IsReady done!") );
console.log("Listening...");
// test change variables with listeners
setTimeout(() => place2IsReady = true, 1000);
setTimeout(() => fromObj.done(), 3000);
function changeListenerFactory() {
const readyListener = (condition, callback, delay) => {
if (!condition || typeof condition !== "function") { return true; }
if (condition()) {
return callback();
}
setTimeout(() => readyListener(condition, callback, delay), delay);
};
return (condition, callback = () => {}, delay = 250) =>
readyListener(condition, callback, delay);
}
Or maybe using a Proxy (with a set trap) works for you
const readyState = new Proxy({ ready: false }, {
set (target, prop, val) {
console.log(`readyState.ready changed from ${target[prop]} to ${val}`);
target[prop] = val;
}
});
console.log("Waiting for changes ...");
setTimeout(() => readyState.ready = true, 2000);
Assuming you can replace place2IsReady with an object:
place2IsReady = {
state: false,
set ready(value) {
this.state = value
state && place_1_call()
},
get ready() {
return state
}
}
place_1_call = () => {
alert('ready')
}
place2IsReady.ready = true
I am new to React so as part of learning I am trying to do a js debounce function when typed in input box (a search box simulation). But for some reason it is not working. The function is getting called each for key up than once for 2000 ms or delay specified in timeout. Below is the link to sandbox code for your reference. I have referred across blogs, the implementations seems the same yet I could not figure out what is the issue.
https://codesandbox.io/s/react-debouncing-9g7tc?file=/src/search.component.js
Issue :
const debounce = (func, delay) => {
let t; // <-- this will be new variable each time function get called
return function() {
clearTimeout(t); // <--- this also points to the new one, so it will clear nothing
// so all the previously called func will be called
t = setTimeout(() => func(), delay);
};
};
1st Solution : useRef
const t = useRef(null);
const debounce = (func, delay) => {
return function() {
clearTimeout(t.current); // <--- pointing to prev setTimeout or null
t.current = setTimeout(() => func(), delay);
};
};
WORKING DEMO
2nd Solution : Define t outside scope of SearchUi
var t;
const SearchUi = () => {
...
const debounce = (func, delay) => {
return function() {
clearTimeout(t);
t = setTimeout(() => func(), delay);
};
};
...
};
WORKING DEMO