When React functional component re-render, does it reassign assigned values & functions? - reactjs

If a code like this re-render by useEffect's dependency,
// ...
const Test = () => {
// ...
const value1 = "test1"
const func1 = () => {
// do something1
}
useEffect(() => {
const value2 = "test2"
const func2 = () => {
// do something2
}
}, [sth])
return (
// ...
)
}
does value1 & value2 & func1 & func2 reassign to memory?
I'm curious about it, related to optimizing.

Short answer, yes. Every time the function runs the old values will be garbage-collected, new primitive values will be assigned in memory and new references will be created to functions and objects.
But the real question with a "not-that-short" answer is "does it impact performance in a significant way?". And the answer is... depends. In most cases, it will not (see the doc about it). But there are scenarios where you will need to make some changes to have a performance optimization with useCallback and use useMemo.
It's also worth mentioning (as said in Shivam Jha's answer) that a trigger in useEffect no necessarily causes a re-render (DOM paint) because this process happens first on the virtual DOM and will only be persisted in the real DOM when necessary.
I will leave here some other references about this discussion.
Dan Abramov's tweet on memoizing everything (also look at responses)
Kent C. Dodds's article about render performance
Felix Gerschau's article about when render occurs

does value1 & value2 & func1 & func2 reassign to memory?
in short the answer is yes.
it's more clear if you look at it for what it is: Test is a function, so everytime that function is called all the variables inside the function scope (the curly brackets) are re-declared and re-assigned.
Let's dive into the details:
value1 and func1 are in the function's body so they get declared and assigned every time the function is called, they are not related at all, they have just the same name.
value2 and func2 instead are declared inside an useEffect hook with a declared dependency (sth), this means that these 2 variables are redeclared and reassigned only after the first render and after every other render if the sth variable changed its value compared to the previous render.
if you want to optimize value1 so it doesn't change at every render you can use the useMemo hook this way:
const value1 = React.useMemo(() => {
return "test1"; //here you might have a more complicate way to determine value1
}, []); //array of dependencies like for `useEffect`, so `value1` will be recalculated only if any of the values provided in here change. by leaving it empty value1 will always be the **same** variable
you can do similar optimizations with functions too with the useCallback hook

According to docs:
What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.
Also, it does not re-renders the code but runs the code again when the dependencies passed to it changes
Tip: Optimizing Performance by Skipping Effects describes solving performance problem due tocleaning up or applying the effect after every render.
Also, you can free up allocated memory (if not freed automatically) or run some side effects after running the code (setTimeOut, etc) by using useEffect with cleanup.
Basically do everything you want to run after useEffect inside a return function:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

Related

Looking for difference between useEffect and code written in the function

My understanding was that useEffect ran whenever the dependencies in the array were rerendered (or only at the first render in if it's blank) or whenever ANYTHING rerendered if there was no Array.
I also thought the code directly in the function about return (not inside a hook though) i.e. like how you declare variables to hold values of setState, or declared functions, only ran "once" (though i'm not sure when)
However, in the example below, I saw the console.log statement run multuple times, almost in tandem with useEffect. Specifically it seemed that the console.log was running in some weird offsync pattern with useEffect, but I don't understand why it would be running, if my variables aren't being redeclared and such. Like, even if it's on every render, similar to a useEffect with no specified dependencies, wouldn't it then also be reintializing the useState variables and such??
So my questions are
When does the code in a "raw" functional component get run, i.e. the console.log("FLOATING CODE") -> I would either think it only ran on initialization OR it ran every rerender, but neither of these seem to be the case. The question is based on the discrepancy between how the functional code reruns, but the initialization code doesnt.
Also why does it run TWICE
why is the value of "log" different on the web page and in the console.log? The setLog is literally before the console.log(), shouldn't it update FIRST? Esp given that the update clearly goes through for the page text to rerender.
import React, { useState, useEffect } from 'react';
const App = () => {
const [num, setNum] = useState(1);
const [log, setLog] = useState('');
const add = () => {
setNum(num + 1);
};
useEffect(() => {
setLog('useEffect has run-> NUM was rerendered ' + num);
console.log(log)
}, [num]);
// setLog(log + " floating code has run");
console.log('\n FLOATING CODE HAS RUN ' + num);
return (
<>
<button onClick={add}>{num}</button>
<div>{log}</div>
</>
);
};
export default App;
Thanks
Also I saw What is the difference between useEffect and code in the body of a functional component? to try and answer my first question, but it didn't explain about declarations, only about the functional difference.
In response to your questions:
The code in the body of a functional component executes every time the component is rendered (your console.log('\n FLOATING CODE HAS RUN ' + num);) In contrast, code in a useEffect fires on the initial render, and then on every render during which the value of one of the elements in the dependency array has changed. In your example, it should run on the first render, and then every time setNum causes the value of num to change.
The reason for it executing twice is difficult to determine, but it may not be an issue. It is likely being caused by internal React mechanisms such as 'strict mode', and will not occur in the production build.
No, setLog should not necessarily execute before the console.log. React setState functions are not guaranteed to execute in sequential order. They should not be regarded as synchronous. You have to take this into consideration when designing your component. This is why, when setting a state that is based on a previous state, you need to pass a callback to the 'set' function with the previous state as a parameter. With that in mind, your const add = () => { setNum(num + 1); }; line is incorrect. It should instead be const add = () => { setNum(prevNum => prevNum + 1); };

Difference between React useCallback with pure function declared inside and outside component

Will there be a difference between these two approaches?:
// outside component
const magicIdx = (i) => (i - 1) / 3
//inside component
const calcIdxOut = useCallback(magicIdx, [])
const calcIdxIn = useCallback((i) => (i - 1) / 3, [])
Does defining a pure function outside the component and using it in a useCallback without dependencies makes any difference?
There's no point in using useCallback with a function declared outside of the component. The purpose of useCallback is to give referential stability to a function, even though that function is technically redefined every render. Since that only applies to functions defined inside the component, it does nothing useful here.
// Stable reference - only declared once
function example() {}
function App() {
// Unstable reference - redeclared each time App renders
function example2() {}
// Stable reference - even though example2 changes,
// example3 reference only changes if dependencies to useCallback change.
const example3 = useCallback(example2, []);
}
So if the above examples make sense, you'll notice that example is already stable, so there's no reason to ever use useCallback on it.
Your function is not expensive, so unless the purpose here is to prevent a memoized child component from re-rendering, referential stability really isn't a large concern anyway.

Best practice for indicating prop is used in useEffect or similar

Often when writing a component in React+Typescript, I want to trigger a useEffect hook on one of the props. For example, consider a button that executes a different function for each consecutive mouse click:
export function ActionButton({clickActions}: {clickActions: (() => void)[]}) {
const [clicked, setClicked] = useState(0);
useEffect(() => {
if (clickActions.length > 0 && clickActions[clicked % clickActions.length]) {
clickActions[clicked % clickActions.length]();
}
}, [clicked, clickActions])
return <button onClick={() => setClicked(clicked => clicked + 1)}/>
}
In this case, clients of this component, need to be aware that they somehow need to prevent clickActions from being a different instance on every render. For example, it could simply be a constant, or be memoized by using useMemo.
Is there a best practice for making my clients aware of this? Are there ways to trigger compile time errors when this rule is violated?
NOTE: I realize this particular case can be solved without using useEffect, but it's just a simple example to illustrate the pattern. I'm not interested in solving this particular problem, but in how to solve the general problem.
There's nothing you can do by way of TypeScript trickery to enforce that clickActions can't change every time the parent renders.
However, you can simply remove clickActions from useEffect’s dependency list. You have to be careful, in general, when doing this, but in this case, it's safe, because the callback you’re passing to useEffect synchronously executes an action when clicked changes, which means the callback will have a reference to the most recent clickActions when it needs it.
This is analogous to the sample operator in RxJS; i.e. clickActions is sampled by clicked.
useEffect(() => {
if (clickActions.length > 0 && clickActions[clicked % clickActions.length]) {
clickActions[clicked]();
}
}, [clicked])
Please also note that even when the parent uses useMemo, it's not guaranteed that it won’t recreate clickActions at a time of React’s choosing:
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.
(see useMemo docs)
It's safe to assume useCallback comes with a similar caveat, although it's not specifically mentioned in the docs.
generally if you have a function that is being passed around its important (especially if its 'created' within a function) to use useCallback when creating it. This ensures that the "props don't change" and thus preventing a rerender.
parentFunction = useCallback(()=>{
if (specialVar==='dance'){
return () => {
console.log('dance')
}
}
else {
return () => {
console.log('do the boogy')
}
}
}, [specialVar])

Why Call React Hooks at the Top Level?

I was reading the React-hooks concepts. I came through a rule which says Don't call React hooks inside conditions.
Here they provided the explanation link.
function Form() {
// 1. Use the name state variable
const [name, setName] = useState('Mary');
// 2. Use an effect for persisting the form
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. Use the surname state variable
const [surname, setSurname] = useState('Poppins');
// 4. Use an effect for updating the title
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}
I understood what they want to say, but I can not get the exact reason, like why I can't use useEffect in if-else block?
There is one more statement
So how does React know which state corresponds to which useState call?
The useState is different call every time and it can return new "[state, setState]" each time, so what is difficult here to know who called which useState?
Bassically, hook rely on a call index. React doesn't know what a given useState() returned as it's state the previous render, but it does know that the first call to useState() by that component returned a [1,2] as it's value, and the second call returned false. Now, if the only thing react knows is what was the given return for a given call index, what do you think could happen if i could write a componente like this:
const [a, setA] = React.useState([1,2,3]);
let c;
if(a === [3,2,1]){
c = React.useState('X');
}
const [b, setB] = React.useState(false);
React.useEffect(() => setA([3,2,1]), []);
now, react knows from the first render that the first call returns [1,2,3] and the second false. then the effect rerenders the component, now it's not the first render so the first call will return the state [3,2,1] since it was updated, the second call (the one c = ...) will return false, but then react sees a third call, what should it return?
From react's point of view, this makes no sense, from your point of view, this can lead to an enormous amout of bugs and problems.
Of course, neither my very basic explanation nor React's are a lot, that's why a come bearing sources, Dan Abramov, one of the people working in react has a very long and detailed post on his blog about this, you can find it here. He also posts a lot of other stuff about how react works behind the curtains, it's worth the read.
It is not about who called which hook useXXXX(i.e useState, useEffect, etc). It is about how hooks are internally implemented and associated with each component. There are a lot of other problems which to solve React relies on the call order of the hooks.
From the docs Hooks FAQ section
How does React associate Hook calls with components?
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state.
Internally hooks are implemented like a queue where each hook represents a node having reference to the next one. The internal structure might look something similar to this,
{
memoizedState: 'a',
next: {
memoizedState: 'b',
next: null
}
}
Take the example of having 4 state variables by calling useState 4 times. With each hook call if the value has not been initialized(i.e on first render) it will initialize the value else read from the memory cell then moving to the next hook internally.
// 4 independent local state variables with their own "memory cell"
// nothing is called conditionally so the call order remains the same
// across renders
useState(1) // 1st call
useState(2) // 2nd call
useState(3) // 3rd call
useState(4) // 4th call
useState(1)
if (condition) { // if condition false hook call will be skipped
useState(2)
}
useState(3)
useState(4)
Now when you call a hook conditionally if the condition is false the hook call will be skipped. This means every subsequent hook call will shift by 1 in the call order resulting in failure to read state value or replacing an effect or many more hard to detect bugs.
So in general it is a bad idea to call any hook conditionally. Only call the hook in top-level(not inside condition, nested functions, or loops) which will help React to preserve the state of hooks for multiple hook calls.
From this answer of React document, it mentioned that hooks are store in a "memory cells" and render in order (" moves the pointer to the next one")
There is an internal list of “memory cells” associated with each
component. They’re just JavaScript objects where we can put some data.
When you call a Hook like useState(), it reads the current cell (or
initializes it during the first render), and then moves the pointer to
the next one. This is how multiple useState() calls each get
independent local state.
Which is match with below section of the link you provided which has some more explanation
// First render
// ------------
useState('Mary') // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm) // 2. Add an effect for persisting the form
useState('Poppins') // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle) // 4. Add an effect for updating the title
// ------------- // Second render // -------------
useState('Mary') // 1. Read the name state variable (argument is ignored)
useEffect(persistForm) // 2. Replace the effect for persisting the
form
useState('Poppins') // 3. Read the surname state variable
(argument is ignored)
useEffect(updateTitle) // 4. Replace the
effect for updating the title
In the second render section, the docs said Read the ... variable means when a useState called in the second time, it doesn't generate new [state, setState], it comes to the "memory cells" to read state value instead and return then we assign it to new array by const [state, setState] = useEffect(). That's why React can guarantee that setState will not be changed each re-render
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.

useState React hook always returning initial value

locationHistory is always an empty array in the following code:
export function LocationHistoryProvider({ history, children }) {
const [locationHistory, setLocationHistory] = useState([])
useEffect(() => history.listen((location, action) => {
console.log('old state:', locationHistory)
const newLocationHistory = locationHistory ? [...locationHistory, location.pathname] : [location.pathname]
setLocationHistory(newLocationHistory)
}), [history])
return <LocationHistoryContext.Provider value={locationHistory}>{children}</LocationHistoryContext.Provider>
}
console.log always logs []. I have tried doing exactly the same thing in a regular react class and it works fine, which leads me to think I am using hooks wrong.
Any advice would be much appreciated.
UPDATE: Removing the second argument to useEffect ([history]) fixes it. But why? The intention is that this effect will not need to be rerun on every rerender. Becuase it shouldn't need to be. I thought that was the way effects worked.
Adding an empty array also breaks it. It seems [locationHistory] must be added as the 2nd argument to useEffect which stops it from breaking (or no 2nd argument at all). But I am confused why this stops it from breaking? history.listen should run any time the location changes. Why does useEffect need to run again every time locationHistory changes, in order to avoid the aforementioned problem?
P.S. Play around with it here: https://codesandbox.io/s/react-router-ur4d3?fontsize=14 (thanks to lissitz for doing most the leg work there)
You're setting up a listener for the history object, right?
Assuming your history object will remain the same (the very same object reference) across multiple render, this is want you should do:
Set up the listener, after 1st render (i.e: after mounting)
Remove the listener, after unmount
For this you could do it like this:
useEffect(()=>{
history.listen(()=>{//DO WHATEVER});
return () => history.unsubscribe(); // PSEUDO CODE. YOU CAN RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[]); // THIS EMPTY ARRAY MAKES SURE YOUR EFFECT WILL ONLY RUN AFTER 1ST RENDER
But if your history object will change on every render, you'll need to:
cancel the last listener (from the previous render) and
set up a new listener every time your history object changes.
useEffect(()=>{
history.listen(()=>{//DO SOMETHING});
return () => history.unsubscribe(); // PSEUDO CODE. IN THIS CASE, YOU SHOULD RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[history]); // THIS ARRAY MAKES SURE YOUR EFFECT WILL RUN AFTER EVERY RENDER WITH A DIFFERENT `history` OBJECT
NOTE: setState functions are guaranteed to be the same instance across every render. So they don't need to be in the dependency array.
But if you want to access the current state inside of your useEffect. You shouldn't use it directly like you did with the locationHistory (you can, but if you do, you'll need to add it to the dependency array and your effect will run every time it changes). To avoid accessing it directly and adding it to the dependency array, you can do it like this, by using the functional form of the setState method.
setLocationHistory((prevState) => {
if (prevState.length > 0) {
// DO WHATEVER
}
return SOMETHING; // I.E.: SOMETHING WILL BE YOUR NEW STATE
});

Resources