So, I have a function:
let animation = null;
const animationRef = (ref) => {
console.log(ref);
animation = ref;
}
And at some point later in the code, I need to access animation. But when I print animation at that point, animation is still null. I am certain that animationRef gets called, because the print of ref is shown in the console.
For context, here is an example from the library that calls animationRef. The reason why I am not putting animation into a state is, that it will get accessed using the event handler of matter.js. Animation is both null if I try to access it inside the Event and outside.
When I put an
useEffect(() => {
console.log(animation);
})
After animationRef, it gets printed, but after the next re-render it is null again.
Related
I was going through useEffect from reactjs docs and I've found this statement here
Experienced JavaScript developers might notice that the function
passed to useEffect is going to be different on every render.
We are passing a function to useEffect and this function is said to be different for each render. useEffect has access to state and props since it's inside the function component and when either of these changes, we can see that change in the function of useEffect(because of closure) right? This is not clear, because in the next line the doc states
This is intentional. In fact, this is what lets us read the count
value from inside the effect without worrying about it getting stale.
To counter this, assume we have a function
function foo(n) {
bar = () => {
setTimeout(() => console.log({n}), 50);
return n;
}
setTimeout(() => {n = 10}, 0);
setTimeout(() => {n = 20}, 100);
setTimeout(() => {n = 30}, 150);
return bar;
}
baz = foo(1);
baz(); //prints 10
setTimeout(baz, 300); //prints 30
It seems that when the closure value(n) is changed, we can see that change in the setTimeout's callback (and this callback isn't changed over time). So, how can the closured value(state/props) in useEffect's function become stale as mentioned in docs?
Am I missing something here? I think it's more of a JS question compared to React, so I took a JS example.
I found the answer a few days back, and as #apokryfos(Thank you again!) mentioned in the comments above, the program execution process is now making more sense. I want to summarize my learnings here.
Firstly, the code I considered, was not like with like comparison (in #apokryfos words) with the React doc statements, and this is true. In case of static HTML + vanilla JS where the HTML button has an event-listener JS function, this function is declared only once and when the event occurs the same JS function is executed everytime.
The code I have given in the question is similar to this, and so when executed in console or in event listener will only be declared once.
In case of React(or any state based UI libraries/frameworks), the HTML is not static and it needs to change on state-change. On the execution side (considering React), component will be created when we call it (in JSX), and the component's function/class will be executed completely from top to bottom. This means
from all the event-handlers that doesn't deal with state, constants to useState's destructed elements and useEffect's callback functions, everything are re-initialized.
If the parent's state changes initiate a render on its children(in normal scenarios), then the children will need to re-render themselves completely with the new props and/or new state to show the updated UI
Considering the example in React docs (link), useEffect with no dependencies will get executed after every render, and here it's updating the DOM by showing the current state value. Unless the callback function has the latest value of that state, it'll only print the stale value. So re-initialising the functions here is the main reason behind not having stale values in the callback functions
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = 'You clicked ${count} times';
});
}
This is a boon and a curse sometimes. Assume if we are sending useState's setState function to the child, and in the child, we have a useEffect(that makes a network call to fetch data) that takes this function as its dependency. Now, for every state change in parent, even if there is a use-case or not, the useEffect will get triggered as this function in dependency is changed (as every update re-initializes the functions). To avoid this, we can utilize useCallback on the functions which we want to memorize and change only when certain params are changed, but it is not advisable to use this on useEffect's callback function since we might end-up in stale values.
Further Reading:
GeeksForGeeks useCallback
SourceCode interpretation of useEffect
Another SourceCode interpretation of useEffect
This is a classic menu case, open on a button click, hide in 5 seconds if no activity. We have 2 state variables. open and active, corresponding to two different states = menu is open, and active (being used). I set the open variable using a button click, then in effect I start the 5 second timeout. Now if the user mouseovers the menu,I set the active property to true and try to clear the timeout. But that is not working. Meaning, the timeout var is always null inside the code that is supposed to clear it.
Here is some code to help you understand:
let [open, openMenu] = useState(false)
let [active, activateMenu] = useState(false)
let timer = null;
useEffect(() => {
if(open && !active) {
timer = setTimeout(() => setOpen(false), 5000)
}
if(open && active) {
if(timer) {
clearTimeout(timer)
}
}
}, [open, active])
// Triggered via the UI on a button click
openMenuhandler() {
setOpen(true)
}
// Triggered via the UI on mouseenter
// identifies that the menu is being used, when mouse is over it
setMenuActive() {
activateMenu(true)
}
// Triggered via the UI on mouseleave
setMenuInActive() {
activateMenu(false)
}
// other code here on
Now the timeout is never cleared. The menu hides in 5 seconds, no matter what. Is there another way to do it? I have even tried moving the clearTimeout to the mouseLeave, but even then the timer is null. How to fix this? Please help.
Whenever your component re-renders, timer variable will be re-declared with an initial value of null. As a result, when useEffect hook executes whenever any of its dependency changes, timer variable is null.
You can solve the problem by making sure that value of the timer variable is persisted across re-renders of your component. To persist the value across re-renders, either save the id of the setTimeout using useRef hook or save it in the state, i.e. useState hook.
I have this state:
const [fingerprintingIssues, setFingerprintingIssues] = React.useState([])
I have a function that loops through a bunch of data to validate. If there are errors it adds them to fingerprintingIssues with setFingerprintingIssues. That works fine.
But in this method (abbreviated) I’m checking for fingerprintingIssues inside of the method’s complete and it always returns []. I’ve read this is an issue with closures and stale state, but I can’t seem to see how that applies here.
const validateDocument = data => {
const start = moment()
try {
Papa.LocalChunkSize = 10485
return Papa.parse(data, {
complete: () => {
// Log completion only if it’s been 20 seconds or more
const end = moment()
const duration = moment.duration(end.diff(start))
const seconds = Math.ceil(duration.asSeconds())
if (seconds < MIN_PROCESS_TIME) {
setTimeout(() => {
setUploadStep('tagging')
console.log('fingerprintingIssues', fingerprintingIssues)
}, (MIN_PROCESS_TIME - seconds) * 1000)
} else {
setUploadStep('tagging')
}
},
error: () => {
setDocumentError(true)
setDocumentReady(false)
},
})
…
Any suggestions?
I think indeed it is an issue with closure and stale state. When using hooks, you need to be cautious about how you use callbacks.
Your component function is called many times (one for each render). Each call will receive a particular state. You can sort of think of it like that particular state is associated with that particular render. If you create a callback within your render, that callback will exist inside the closure of the render it was created in. The issue is that if your component is re-rendered, the callback still exists in the closure of the old render, and will not see the new state.
One way around this is to use React.useRef, which you can read more about here. Refs hold a value and can be modified and accessed at any time regardless of closure. Note that modifying the ref does not cause your component to be re-rendered.
const fingerPrintingIssues = React.useRef([])
const validateDocument = data => {
// Perform validation
// Now access (refs are accessed by .current)
console.log(fingerPrintingIssues.current)
}
// Modify:
fingerPrintingIssues.current.push(...)
In case you need to re-render your component when modifying the value, you can use combination of useRef() and useState().
I want to call a function if the object move Event is finished.
Currently it Looks like this:
canvas.on('object:moving', function (event) {
recCanvas(canvas) // fire this if finished
});
The issue is that it fires the function everytime an object is moved how can I prevent this and fire the function once the moving finished.
What event happened when move event is finished?
Mouse up will finish event object moving. So you need a boolean variable to check when object is moving, then on mouse up if object has been moved call you function:
var isObjectMoving = false;
canvas.on('object:moving', function (event) {
isObjectMoving = true;
});
canvas.on('mouse:up', function (event) {
if (isObjectMoving){
isObjectMoving = false;
recCanvas(canvas) // fire this if finished
}
});
There is an object event when the object is finished moving
canvas.on('object:moved', function(event){
recCanvas(canvas) // fire this if finished
});
Yeah i know this is like pretty late and you probably found a suitable answer, but in the current version of Fabric js, if you try to use the above marked answer or try to use the object:modified event, you are likely going to encounter some bugs, not immediately but here is what I found;
when you use the mouse:up or object:modified to know if object position is changed, when you try to drag the object to change the position a couple of more times to be sure if it's working great, the browser page might stop responding, and sometimes it will take time before the canvas responds and the value to get updated.
So here is what I do, although i am using react js with fabric js so I am not fully sure if you who are using just vanilla javascript encounter this problem but in case someone also using react search for this problem here is a solution.
use a useEffect hook or onComponentDidMount, and inside it create a setInterval that get the currently active object after every 0.5 seconds or whatever time you think will suit you, and then try to update the variable and also don't forget to clear the interval.
//import your fabric js and react
const GetActivePosition = () => {
const [pos, setPos] = useState({left: 0, top: 0})
useEffect(() => {
const active = canvas.getActiveObject()
let interval = undefined
if (active){
interval = setInterval(() => {
setPos({left: active.left, top: active.top})
}, 500)
}
return () => clearInterval(interval)
}, [])
return (
<>
<p>Left: {pos.left}</>
<p>Top: {pos.top}</>
</>
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I have a function stopRecording() that I'd like to be called when a timer runs out, or when someone presses a stop button. The problem is that when it is called when the timer runs out (the first half of the render function) it is called continuously, despite me bracketing it in an if clause. When it is called as a button event (in the return half of the render function) then it works fine.
Note my console logs. When I open the console in Chrome and let the timer run out, the console logs I marked as successful in my code body runs, but NOT ones that I commented with //!!!. I also get the following error continuously: Invariant Violation: setState(...): Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.
//...
stopRecording: function() {
if (this.state.recording){
console.log("this will log as expected")
this.setState({recordingStatus:"",
recording:false})
console.log("this will NOT log as expected'") //!!!
this.props.emit("recEmit")
}
}
render: function() {
var timeBar;
var countdown = "0";
var timeBarFill = "#FF9090"
if (this.state.recording){
countdown = new Date()-this.state.startTime
timeBarFill = "#FF3830";
if (countdown > this.state.maxRecLength){
console.log('this will log as expected')
countdown=0
this.stopRecording()
console.log('this will NOT log as expected') //!!!
};
}
//...
return(
//...
<button type="button" id="button" onClick={this.stopRecording}><b>Stop</b></button>
//...
)
You should never call setState inside render(): https://github.com/facebook/react/issues/5591#issuecomment-161678219
As render should be a pure function of the component's props and state, which means that it should not have any side effects (like changing its own state).
Also, you can't guarantee that React will call your component's render() method when your countdown is about to expire. Consider using setTimeout in component's life cycle methods.
I think that this is due to how states work in react. This article explains it pretty well. I suggest to read it but I can some it up for you:
setState is usually called asynchronously.
if setState is not triggered by an event that React can keep track of, such as onClick, it is called synchronously.
This means that when you are using onClick everything goes fine because your call of setState in stopRecording does not block and the function finishes before a re render is called. When a timer triggers it this happens synchronously, the state changes and render is called again.
Now, I still do not understand how it can run continuously, since it should have set the state.recording variable to false and I don't see anything that turns it back to true.
Also, be careful to use states just for variables that are truly states: change with time. The maxRecordinLength does not seem to be a state variable, and same for startTime.
EDIT:
after I saw the update I realized that the main issue here is changing a state inside of the render method. I posted this link in a comment here but I think it is worth explaining.
Basically, you can solve your issue by calling a setTimer function in the componentDidMount function of react-- more on this here.
Something like:
componentDidMount: function(){
setTimer(this.myFunction, this.props.maxRecLength);
},
And you myFunction would look like this:
myFunction: function(){
this.setState({timeElapsed: true});
},
Then you can use this.state.timeElapsed in your render function, and whatever is in there will be displayed after the maxRecLength is reached.