I am creating a component with an animation that occurs with a css class toggle. Sandbox of the example here.
The css class is applied conditionaly against the transitioned field, so we should get an animation when the transtioned field goes form false to true.
Problem:
The animation doesn't happen in the case where the state if modified like this :
animateWithoutST = () => {
this.setState({transitioned: false},
() => this.setState({transitioned: true}))
}
But it works if it the second setState is called within a setTimeout callback like this:
animateWithST = () => {
this.setState({ transitioned: false },
() => {
setTimeout(() => this.setState({ transitioned: true }))
}
)
}
Why isn't animateWithoutST working as expected although my component is rendering in the right order ?
This looked definitely bizarre and I had to dig into it until I understood what is happening.
So, yeah without the setTimeout it doesn't work, not even in the componentDidUpdate method and there is an explanation: you are changing the state, and it is updated, and render is called twice BUT because of browser optimization we don't see the animation happening: "browsers are not re-rendering stuff that changed in the same animation frame".
When you use the setTimeout you are forcing the 2nd state update to go into the next animation frame and voila you see the animation. Even with the current timeout value set to 0, it might not work on some browsers, but if you set the value to > 16ms aprox, it prob will work always (you need a value greater than an animation frame).
You can also use requestAnimationFrame, twice to assure your both state updates fall into different animation frames, check it here.
I found all this info in this article, check it because it is extremely well explained. Does now make sense to you?
You should be using componentDidUpdate instead:
componentDidUpdate(prevProps, prevState) {
if(!this.state.transitioned) {
this.setState({
transitioned: true
});
}
}
This makes sure that your second setState gets called AFTER the component has updated.
setState is asyncronous which means calls do not happen back to back. So multiple calls to setstate might not happen as you expect them to. Read this to know more about setstate()
Related
setState updates state asynchronously. It's my understanding that, when using a class component, you can do something like this to ensure certain code is executed after one setState updates state:
setState({color: red}, callbackThatExecutesAfterStateIsChanged);
I'm using a functional component & hooks. I'm aware, here, useEffect()'s callback will execute everytime after color state changes and on initial execution.
useEffect(callback, [color]);
How can I replicate similar behaviour as the class component example - that is, to execute a chunk of code once after one setState() successfully changes state and not on initial execution?
If you ask me, there is no safe way to do this with hooks.
The problem is that you both have to read and set an initialized state in order to ignore the first update:
const takeFirstUpdate = (callback, deps) => {
const [initialized, setInitialized] = useState(false);
const [wasTriggered, setWasTriggered] = useState(false);
useEffect(() => {
if (!initialized) {
setInitialized(true);
return;
}
if (wasTriggered) {
return;
}
callback();
setWasTriggered(true);
}, [initialized, wasTriggered]);
};
While the hook looks like it works, it will trigger itself again by calling setInitialized(true) in the beginning, thus also triggering the callback.
You could remove the initialized value from the deps array and the hook would work for now - however this would cause an exhaustive-deps linting error. The hook might break in the future as it is not an "official" usage of the hooks api, e.g. with updates on the concurrent rendering feature that the React team is working on.
The solution below feels hacky. If there's no better alternative, I'm tempted to refactor my component into a class component to make use of the easy way class components allow you to execute code once state has been updated.
Anyway, my current solution is:
The useRef(arg) hook returns an object who's .current property is set to the value of arg. This object persists throughout the React lifecycle. (Docs). With this, we can record how many times the useEffect's callback has executed and use this info to stop code inside the callback from executing on initial execution and for a second time. For example:
initialExecution = useRef(true);
[color, setColor] = useState("red");
useEffect(() => {
setColor("blue");
});
useEffect(() => {
if (initialExecution.current) {
initialExecution.current = false;
return;
}
//code that executes when color is updated.
}, [color]);
I'm experiencing some performance issues with a react application that I developed. These issues specifically (or most notably) occur with Firefox (both FF developer 77.0b7 and FF 76.0.1).
When using this application in Firefox, CPU usage gets extremely high, and my fans start spinning up to very high speeds. I get about 15-19fps in firefox according to the performance tools in FF. I get roughly 60fps in Chrome and Safari.
These issues occur when I begin typing into the input field, and get worse as the input gets longer (which makes sense)
The application is available here:
https://text-to-aura-generator.netlify.app/
Source code available here: https://github.com/paalwilliams/Text-to-Aura/tree/master/src
I'm almost certain that this is something I'm doing incorrectly, or that I've written the code inefficiently, but that isn't necessarily supported by the stark performance difference between browsers. Is chrome just that much better and handling react/constant rerenders?
I know that this is a broad question, but I honestly don't understand what is happening here, or necessarily how to troubleshoot it beyond the developer tools. Any input or thoughts would be greatly appreciated.
The problem is your application is rendering too fast. In your particular case, there a few ways to improve that.
Every time you update the state, React needs to re-render your application, so updating the state within a loop is usually a bad idea.
Also, you are using useState 3 times, but only colors should be there, as App actually needs to re-render to reflect the changes there. The other two pieces of state (text and hex) are only being used to pass data from the handleChange to the callback inside useEffect.
You can restructure your code to:
Avoid updating the state within a loop.
Use a simple variable instead of state.
Use useCallback to define a function with that logic that is not re-created on each render, as that forces TextInput to re-render as well.
Throttle this callback using something like this:
import { useCallback, useEffect, useRef } from 'react';
export function useThrottledCallback<A extends any[]>(
callback: (...args: A) => void,
delay: number,
deps?: readonly any[],
): (...args: A) => void {
const timeoutRef = useRef<number>();
const callbackRef = useRef(callback);
const lastCalledRef = useRef(0);
// Remember the latest callback:
//
// Without this, if you change the callback, when setTimeout kicks in, it
// will still call your old callback.
//
// If you add `callback` to useCallback's deps, it will also update, but it
// might be called twice if the timeout had already been set.
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// Clear timeout if the components is unmounted or the delay changes:
useEffect(() => window.clearTimeout(timeoutRef.current), [delay]);
return useCallback((...args: A) => {
// Clear previous timer:
window.clearTimeout(timeoutRef.current);
function invoke() {
callbackRef.current(...args);
lastCalledRef.current = Date.now();
}
// Calculate elapsed time:
const elapsed = Date.now() - lastCalledRef.current;
if (elapsed >= delay) {
// If already waited enough, call callback:
invoke();
} else {
// Otherwise, we need to wait a bit more:
timeoutRef.current = window.setTimeout(invoke, delay - elapsed);
}
}, deps);
}
If the reason to use useEffect is that you were not seeing the right values when updating colors, try using the version of setState that takes a callback rather then the new value, so instead of:
setColors([...colors, newColor]);
You would have:
setColors(prevColors => ([...prevColors , newColor]));
The most common performance issues with react come from setting the state too many times since you're constantly re rendering the page and the elements within it.
I'm going to assume here that my problem lies within the async nature of how React states work (at least I hope that's a correct statement). I have an app where I have created an UI in which there are 4 buttons with values in them and an "OK" button. The user tries to choose the largest value by clicking the corresponding value button and then clicking "OK" to confirm their choice.
When, why and how does React update my this.setState({ value: this.state.chosenButton }); statement?
Because in the
if (...) {
//...
} else {
this.onAnswer(this.state.value, item.id);
}
part value has still not updated.
I've tried creating a separate function called stateUpdated which houses the setState call, timeouts and other ways of delaying the execution to allow for the state to update, but it seems the issue is not time based but something else completely.
I am also aware that I could just use chosenButton in the last else statement instead of the value but I am more interested in understanding the "Why?" of this problem and not how to "fix" my code as such.
keyInput(event) {
const moduleState = StudentModuleState;
const item: Item = moduleState.displayedItems[0];
const practice: boolean = !StudentModuleState.itemSet.assessment_set;
if (!this || !this._isMounted) { return; }
this.setState({ value: this.state.chosenButton });
if (practice) {
if (this.state.chosenButton === item.correct) {
this.setState({ answerCorrect: true })
setTimeout(() => this.progressHandler(), 2000);
} else {
this.setState({ answerWrong: true, })
setTimeout(() => this.progressHandler(), 2000);
}
} else {
this.onAnswer(this.state.value, item.id);
}
}
I'm going to assume here that my problem lies within the async nature of how React states work...
That's right. The state update is asynchronous, so code immediately following the setState call still sees the old state. To wait until it's updated, use the update callback (the second argument to setState):
keyInput(event) {
const moduleState = StudentModuleState;
const item: Item = moduleState.displayedItems[0];
const practice: boolean = !StudentModuleState.itemSet.assessment_set;
if (!this || !this._isMounted) { return; }
this.setState(
{ value: this.state.chosenButton },
() => {
if (practice) {
if (this.state.chosenButton === item.correct) {
this.setState({ answerCorrect: true })
setTimeout(() => this.progressHandler(), 2000);
} else {
this.setState({ answerWrong: true, })
setTimeout(() => this.progressHandler(), 2000);
}
} else {
this.onAnswer(this.state.value, item.id);
}
}
);
}
Side note about this:
this.setState(
{ value: this.state.chosenButton },
// ------^^^^^^^^^^^^^^^^^^^^^^^
It appears you're updating state in response to a button press (remembering which button was pressed), and then using that updated state in response to a keyboard event. That's okay only because React specifically handles it: It guarantees that the previous state change in response to a click will be rendered (and thus applied) before the next event is dispatched. These used to be called "interactive" events, but now are called "discrete" events, you can find a list here. Note that this is for click and various keyboard events, and not for things like mousemove. Details in this twitter thread, where Dan Abramov (a core committer on the React project) writes:
Even in Concurrent Mode, we indeed guarantee React events like “click” and others that imply intentional user action will flush before the next one is handled. Your “disabled” example is one of the motivations.
Note we don’t guarantee first click is processed synchronously. Only that if you click the next time, we’ll make sure to flush the results of the first one before we decide whether to handle the next event or ignore it.
You can find a list of such events here. (Called “interactive” in code now although that might not be the best naming). https://github.com/facebook/react/blob/master/packages/react-dom/src/events/SimpleEventPlugin.js
We don’t make such guarantees for events like “mousemove” which are continuous rather than discrete. For those we assume it’s safe to batch and sometimes skip intermediate ones as user doesn’t intentionally think about each move as a separate event.
Also note in Concurrent Mode these guarantees only are enforced for React events. If you subscribe manually via addEventListener() there’s some extra stuff you’ll need to do to have them.
However, today (in sync mode) those are always sync. So just something for the future.
I'am puzzled by the setState() accepted object.The code link is here https://codepen.io/DRL9/pen/jadbWq and the code is as follow:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
intervalCount: 1,
buttonCount: 1
};
this.increment = 1;
this.intervalId = null;
}
tick() {
this.setState({
intervalCount: this.state.intervalCount + this.increment
});
this.setState({
intervalCount: this.state.intervalCount + this.increment
});
}
onClick() {
this.setState({
buttonCount: this.state.buttonCount + this.increment
});
this.setState({
buttonCount: this.state.buttonCount + this.increment
});
}
componentDidMount() {
this.intervalId = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.intervalId);
}
render() {
return <div>
<div>
interval counter: {this.state.intervalCount}
</div>
<button onClick={this.onClick.bind(this)}>increment</button>
<div>
button counter: {this.state.buttonCount}
</div>
</div>;
}
}
I expect that intervalCount will increment 1 like the behavior when I click increment button. However, it increment 2 each tick.
The only different is that one updated is in setInterval function and the other updated is in onClick function.
Why are their behavior different?
We can't talk in absolutes regarding the timing of setState as it is, by definition, unpredictable. The state changes may be delayed to some time in the future, and this behavior may be different depending on the version of React that you are using.
In the example provided, React is delaying state updates until the onClick handler has finished running. React knows when this handler is finished running because we are passing the handler through JSX's onClick (which is then processed internally by React):
// React processes the onClick handler below
<button id="btn" onClick={this.onClick.bind(this)}>increment</button>
If we were to instrument the onClick logic ourselves, by manually grabbing the button element from the DOM and adding a click event listener that calls our onClick handler, the button updates identically to the setInterval (React doesn't know that we are updating state within a click handler, so it chooses not to make the optimization of batching the calls to setState).
See this codepen, where the button counter has a click handler manually added to it in the componentDidMount function as opposed to using JSX's onClick. Notice that the button counter now increments in intervals 2 instead of 1.
I want to stress that this behavior is not deterministic and you should never use this.state within your setState function. Instead, you want to use the variation of setState that accepts an updater function that contains the previous state. Then, build your new state from the state passed to the updater:
this.setState(state => ({
buttonCount: state.buttonCount + this.increment
}));
See this codepen, which uses an updater to update the button counter, producing the expected effect of updating the button counter in intervals of 2.
For more info on setState see the official documentation.
From the documentation for setState:
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
This is saying that when you reference state data (this.state.buttonCount or this.state.intervalCount) immediately after you've changed it using setState (as you do in both functions on the second setState command) the behavior will be unpredictable. Maybe setState immediately updates the state data, as it seems to be doing with intervalCount, and maybe setState waits to update the state data so it can batch it later, as it seems to be doing with buttonCount. As a developer we should avoid exposing ourselves to such unpredictable behavior by using other variables when we want to modify the state multiple times during the same event.
As to why intervalCount is fairly consistently being updated immediately (and thus incrementing the second time) and buttonCount is consistently being batched (and only incrementing one time for the two calls to setState) my guess is this: onClick is triggered by a user interaction so the React engine probably guesses that during user interactions a lot of state may be changing, so it batches the calls to setState, maybe until the event fully propagates. tick, on the other hand, is triggered by an internal callback without any user interaction being processed, so the React engine probably guesses it's safe to update state right away without batching.
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.