Generators are called more than expected in React - javascript

I found a behavior where the generator appears to be called twice.
The following is a simple code that gets a number from the generator and output it to console.
It expects 0 and 1 to be output to the console, but in fact it outputs 0 and 2.
import { useState, useEffect } from "react";
function* counter() {
let val = 0;
while (true) yield val++;
}
const count = counter();
function App() {
console.log("rendered: count = ", count.next().value);
const [hoge, setHoge] = useState("first");
console.log("rendered:", hoge);
useEffect(() => setHoge("second"), [setHoge]);
return <div>{hoge}</div>;
}
export default App;
demo:
https://codesandbox.io/s/friendly-http-g84cp?file=/src/App.tsx
Not only to useEffect, I also found the same behavior with setInterval. Also, if we remove <React.strict>, the console outputs 0 and 1 as expected.
Do you have any idea why this behavior is happening?

As per the docs,
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
That Function component bodies being invoked twice is the cause of your problem. That means on initial mount, App function body will be invoked twice and then again on setting a new state, App function body will be invoked twice. By that logic, you should have seen 4 more logs i.e 8 in total.
Here is where the following exception comes :-
Starting with React 17, React automatically modifies the console
methods like console.log() to silence the logs in the second call to
lifecycle functions. However, it may cause undesired behavior in
certain cases where a workaround can be used.
To actually visualize all the 8 logs, you can just do let log = console.log at the top level and replace usage of console.log by log and you will see what's actually happening.
Simply said, don't put such a behavior inside a function body since it's a side-effect for which Strict-mode came into being.
Here is a forked codesandbox to see this :-

The <React.StrictMode> exists to detect precisely what you're doing in your code which is an anti-pattern (side-effect in the render function).
This mode executes the lifecycle methods of the components twice on purpose (in your case it's a functional component so the function is being executed twice), while swallowing the console.log by monkeypatching it.
That's why you observe the generator increment without any log in the console.

Related

Different function gets passed to useEffect on every render? - ReactJS useEffect

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

React - in a functional component, using hooks, can I execute a piece of code once after one setState() successfully changes state?

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

How to skip running useEffect on first run is Strict mode?

I have a custom hook that I use when I need to skip running of the useEffect function on a first call. This works like a charm in non Strict mode.
hooks/useEffectSkipFirst.js
import { useCallback, useEffect, useRef } from 'react';
export default (fn, deps) => {
const isFirstRun = useRef(true);
const execFunc = useCallback(fn, deps);
useEffect(() => {
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
execFunc();
}, [execFunc])
};
Usage:
import useEffectSkipFirst from 'hooks/useEffectSkipFirst';
useEffectSkipFirst(() => {
// fetch new user by ID
}, [selectedUser.id])
Obviously, this hook works completely differently in Strict mode where it will execute a function on the first call because the useEffect hook will be called twice. The reason why it will be called twice is the fact that its dependency is a variable returned from useState (or react-redux's useSelector) which gets called twice in Strict mode. Therefore it makes an API call on the first run while it shouldn't until the user changes.
The only way I can fix this is by listening to NODE_ENV variable, and in case of development, I would block execution of the function until 3rd call while blocking until 2nd call in case of other envs. Unfortunately, that sounds like a poor solution to me.
Is there a better way to write a custom hook that works in both modes? Bear in mind that it is important to write a hook that doesn't throw any eslint-plugin-react-hook warnings.
EDIT: Epic facepalm on my end. The reason why everything works for me in non Strict mode and doesn't in Strict is that all my routes are rendered through Private.js component which checks whether I already have a user in store (logged in) or not. In case I don't have a user it will make an API call. Because I'm doing the check within render function (wrong to have side effects in that check) and render is called twice in Strict mode I'm actually making 2 API calls (bug). That also means my Redux store gets updated twice and thus triggers my useEffect twice which has that variable as a dependency. I've been looking for the bug in the wrong place the whole time. I apologize for wasting your time.
Don't know if you still need my answer but I could come across with this workaround:
setTimeout(() => (isFirstRender.current = false), 1000);
By setting the ref to false only after 1 second of timeout, React can run its useEffect as often as it can, the if check will fail on the first render. With that I can easily start my countdown timer only onClick so if my dependency changes.

Why does the load() function with [] as the second argument not run on mount unless I pass in [load] to the second argument?

Why does the load() function with [] as the second argument not run on mount unless I pass in [load] to the second argument? I do not want to pass in the second argument as I only want the load() function to run once on mount and never afterward. How do I achieve this?
const HoursTimer = (props) => {
const [isActive, setIsActive] = useState(null);
const [activeId, setActiveId] = useState(null);
useEffect(() => {
load();
}, [])
function load() {
props.hours.forEach((hour, i) => {
if (i === (props.hours.length-1)) {
setIsActive(hour.clockedIn)
setActiveId(hour._id)
}
})
}
return (
<div>
<p>{isActive}</p>
<p>{activeId}</p>
</div>
);
};
function mapStateToProps( { hours } ) {
return { hours };
}
export default connect(mapStateToProps, { fetchHours })(HoursTimer);
I've never used React before, but a quick review of the useEffect source, and I think I have and answer for you.
useEffect takes two arguments, as you know. But did you know the array, aka "deps" tells React, when you want to rerun the effect? React does this by "rechecking" those deps.
So a short hand for run once, but never again is an empty array.
I confirmed this from React's documentation
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
If you pass an empty array ([]), the props and state inside the effect
will always have their initial values. While passing [] as the second
argument is closer to the familiar componentDidMount and
componentWillUnmount mental model, there are usually better solutions
to avoid re-running effects too often. Also, don’t forget that React
defers running useEffect until after the browser has painted, so doing
extra work is less of a problem.
Another insightful comment from the documentation
The array of dependencies is not passed as arguments to the effect
function. Conceptually, though, that’s what they represent: every
value referenced inside the effect function should also appear in the
dependencies array. In the future, a sufficiently advanced compiler
could create this array automatically.
So depending on you desired behavior, if you want to run the effect, everytime the prop.hours updated you might need to do something like
useEffect(() => {
load();
}, [props.hours])
function load() {
props.hours.forEach((hour, i) => {
if (i === (props.hours.length-1)) {
setIsActive(hour.clockedIn)
setActiveId(hour._id)
}
})
}
Looking into things a bit more, I found this article, which may be helpful.
As for you question:
Why does the load() function with [] as the second argument not run on
mount unless I pass in [load]
I suspect, it's because [load] is a function, which react, will rerun, but
it's doing so to check if it needs to rerun the effect... with the side effect, of well, having already, rerun load, which just so happens to be your entire useEffect. -- that was at least 3 levels of inception.
Hope that helps.

React Performance Issues in Firefox?

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.

Categories