Why Call React Hooks at the Top Level? - reactjs

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.

Related

How should I use selectors in Redux Toolkit?

I am learning Redux Toolkit. From a React POV it seems very intuitive to access whatever part of the state you need from within useSelector using an inline arrow function, and then conduct any calculations. As an example consider a cart item with its data (like item count) in redux store.
function CartItemCounter({ itemId }){
const cart = useSelector(state => state.cart);
const itemInCart = cart.items[itemId];
const count = itemInCart?.count || 0;
return <div>{itemId} - {count} nos</div>
}
But I'm seeing all this information saying you should define your selectors beside your slice, use createSelector and all. What is the right way, and why is it better?
The information that is out there is essentially talking about different levels of optimization while using useSelector.
What you need to understand before anything else, is how useSelector works internally.
How does useSelector work?
When you pass a function to useSelector (obviously inside a react component), it essentially hooks on to the global redux state. Whenever any change happens in any part of the global state (i.e. when dispatch() is called from any part of the app), redux will run all the functions you passed to useSelector in your app, and perform certain checks.
Redux will take the result from each function, and compare it to the value it got the last time it ran the same function.
How does it make this comparison?
It uses a reference equality for this comparison. So if redux has to think that the result of the function hasn't changed, either the value returned from the function has to be a primitive and equal.
4 === 4 // true
'itemA' === 'itemA // true
Or, the value returned must be a derived data type (arrays, objects), with the same reference. So essentially the same object.
const x = { name: 'Shashi' }
const fn1 = () => x;
const fn2 = () => x;
const fn3 = () => { name: 'Shashi' }
fn1() === fn2(); // true
fn1() === fn3(); // false, because the objects are different, with different references
In practice, redux changes the wrapping object if either a key (or key of a nested object) is changed, or you manually change the entire object using a dispatch action (This is related to the immer library integration). This is similar to how you would do in regular React.
/* See how most keys are spread in, and will hence maintain reference equality.
While certain keys like 'first', 'first.second', 'first.second[action.someId]'
are changed with new objects, and so will break reference equality */
function handwrittenReducer(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue,
},
},
},
}
}
Otherwise it maintains the same objects within its state, and returns the exact same objects with the same references, when you access them. To verify this, if you access your cart twice, its literally going to be the same object.
const cart1 = useSelector(state => state.cart)
const cart2 = useSelector(state => state.cart)
cart1 === cart2; // true
What does it do with this comparison?
If the comparison returns true, i.e. the new value is the same as the old value, Redux tells that instance of useSelector to chill tf out, and not do anything. If it returns false however, it tells that component to re-render. After all, the value you are accessing from state has "changed"(according to Redux laws), so you probably want to show the new value.
With this information, we can make change the kind of function we pass to the useSelector, in order to get certain optimization benefits.
Optimization Levels
Level 0: Accessing slice data inline
const cart = useSelector(state => state.cart)
// extract the information you need from within the cart
const itemInCart = cart.items[itemId];
const count = itemInCart?.count || 0;
This is not a good way to access the data. You actually need a subset of the data from the cart, but you are fetching the whole thing, and doing the calculation outside the selector.
Problems:
When you put stuff like this inline, what happens if you change the shape of your data in the future? You have to go to every place that uses useSelector and manually change it. Not so good.
More importantly, every time any part of the cart changes, the entire cart object actually changes. So Redux sees your component that asks for the cart, and thinks
The cart has changed. This component is asking for the cart. It should probably re-render.
BAM Every single instance of this component rerenders. And for what? The count of the item you're referencing probably didn't change. So ideally there shouldn't have been a re-render.
Level 1: Centralize the selector
An easy optimization is to put the selector function in a centralized location next to your slice. That way, if your data shape changes in the future, you can just change it in one place, and your whole app (wherever it uses that data) will work with the new shape.
// inside or next to the slice file
const selectCart = (state) => state.cart
//...
// somewhere inside a react component
const cart = useSelector(selectCart)
Level 2: Access the relevant data
Since redux is comparing the results of your selector function, if you want to avoid unnecessary rerenders, you want to make sure the results have reference equality (===). So target the exact value you wish to look at, in your selector.
// extract the information you need from within the cart, *within the selector*
const count = useSelector(state => state.cart.items[itemId]?.count || 0)
// You don't have to use a one-liner, a multi-line function is better for readability
When Redux executes these functions, it keeps a record of the value returned from these selector functions, for each individual useSelector. This time the values are going to be the same for every single counter, except the one that actually changed. All those other counters that didn't actually change in value don't have to unnecessarily re-render anymore!
And if any of you folks think this is premature optimization, the answer's no. This is more along the lines of putting a dependancy array on your useEffects to avoid infinite loops.
Not forgetting the Level 1 optimization, we can also extract this function centrally
const selectItemById = (state, itemId) => (state.cart.items[itemId]?.count || 0);
function CartItemCounter({ itemId }){
//...
// somewhere inside a react component
const count = useSelector((state) => selectCart(state, itemId))
//...
}
So that solves all of our problems right?
For now, yes. But what if this selector function has to run some expensive computation.
const selectSomething = (state) => reallyExpensiveFn(state.cart)
//...
// somewhere inside a react component
const cart = useSelector(selectSomething)
You don't want to keep running that do you?
Or what if you have no option but to return new objects from your select function. A common scenario for this case would be returning a subset of data from the state.
const selectFilteredItems = (state) => state.itemsArray.filter(checkCondition) // the filter method will always return a new array
//...
// somewhere inside a react component
const cart = useSelector(selectFilteredItems) // re-renders every time
To solve this you would have to memoize or cache the results from the function call. Essentially you would need to make sure that if the input arguments are the same, the result will maintain reference equality with the previous result. This introduces the need to maintain some kind of cache state.
Level 3: createSelector
Fortunately, Reselect library, which is reexported with Redux Tookit, does this work for you. You can take a look at the redux toolkit for the syntax.
const selectFilteredItems = createSelector(
(state) => state.itemsArray, // the first argument accesses relevant data from global state
(itemsArray) => itemsArray.filter(checkCondition) // the second parameter conducts the transformation
)
//...
// somewhere inside a react component
const cart = useSelector(selectFilteredItems) // re-renders only when needed
Here the second function is called the transformation function, and is where we put the expensive computation, or the function that returns inconsistent references as a result (filter,map etc).
The createSelector caches
a) the arguments to the transformation function
b) the result of the transformation function
of the previous call to the selectFilteredItems function. If the arguments are the same, it skips executing the transformation function, and gives you the result you got the last time it was executed.
So when useSelector looks at the result, it gets reference equality. Hence the re-render is skipped!
One little caveat here is that createSelector only caches the very previous result. This makes sense if you think about a single component. In a single component you are only concerned about differences in values and results compared to the previous render. But in practice, you are likely to share selectors across multiple components. If this happens, you have a single cache location, and multiple components using this cache. i.e. Stuff breaks.
Level 4: createSelector factory function
Since the logic for your selector is the same, what you need to do is run createSelector for each component that uses it. This creates a cache for each component, giving us the desired behaviour. In order to do this, we use a factory function.
const makeSelectFilteredItems = () => createSelector(
(state) => state.itemsArray, // the first argument accesses relevant data from global state
(itemsArray) => itemsArray.filter(checkCondition) // the second parameter conducts the transformation
)
//...
// somewhere inside a react component
const selectFilteredItems = useMemo(makeSelectFilteredItems,[]); // make a new selector for each component, when it mounts
const cart = useSelector(selectFilteredItems) // re-renders only when needed
You intend to make a new selector (and by extension, cache) for each new component that mounts. So you put it inside the actual component function and not on the module scope. But this will re-run makeFilteredSelector for each render, and hence create a new selector for each render, and hence eliminate the cache. This is why you need to wrap the function in a useMemo with an empty dependency array. It runs on every mount.
And voila!
You now know where, why and how to use selectors in Redux. I personally feel that the createSelector syntaxes are slightly contrived. There is some discussion on changing cache sizes going on. But for now I feel that sticking to the docs should get you through most situations.
But I'm seeing all this information saying you should define your selectors beside your slice, use createSelector and all.
That's the way to go if you're deriving something from the state, which ends up being an expensive computation or something that's reused often throughout your app. Imagine, for example, your state.cart can contain 50.000 items and you need to sort them from most expensive item to least expensive. You don't want to re-calculate this all the time because it slows your app down. So you cache/memoize the result.
What is the right way, and why is it better?
The right way is to use memoization helpers like createSelector when/if you want to avoid expensive computation. Most people optimize prematurely, so I'd just stick to useSelector and keep it simple if in doubt.

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

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

When exactly do React Hook states get set?

I am trying to understand the order of execution for the following piece of code involving React Hook states:
const App = () => {
const [ searchedCountry, setSearchedCountry ] = useState('');
const [ filteredArr, setFilteredArr ] = useState([]);
const [ filteredLength, setFilteredLength ] = useState(0);
//...
const filterCountries = (event) => {
event.preventDefault();
setFilteredArr(countries.filter(country => country.name.includes(searchedCountry)));
setFilteredLength(filteredArr.length);
console.log("filtered arr length", filtered.length);
}
//...
}
When filterCountries is triggered, setFilteredArr sets the state, filteredArr, to an array filtered by a query. However, when exactly does filteredArr get set?
filteredArr.length returns 0, meaning filteredArr has not been set yet, even after calling setFilteredArr.
At first, I thought by executing setFilteredArr, the component re-renders, causing execution to skip the method calls after setFilteredArr. That would explain why filteredArr.length is 0. However, the console.log is still called, meaning after a component re-renders, the order of execution is actually resumed.
What is going on? When exactly does filteredArr get set?
The thing to remember is: all your state variables are local variables. They only exist for this particular time the component rendered. So on the first render, console.log("filtered arr length", filteredArr.length); is referring to the array that exists on that first render. filteredArr will never be assigned a new array (it can't, it's a const), and unless you mutate the array (which you shouldn't), the length of that array will always be 0.
When you call setFilteredArr, this instructs react to rerender the component. React might do the rendering synchronously, or it might wait to try to batch up changes. When that 2nd render happens, you make a call to useState and get back the new value. This is assigned to a local variable named filteredArr, but this is a completely different variable than the one we had on the first render. The console.log statement in that first render, will have no way to access the variable in the second render. But the second render has access to it, so any logging you do the second time will show the second array.
setState or 'setFilteredLength' is an async operation. After you call it, it will take some time to update the state (as it is async, it will not wait for that update. It will simply execute the next line) so when it executes the console.log the value has not changed -> yet

useEffect pushing value to array only once ReactJs

So basically i started learning ReactJs and am trying to build a chatting app
now here there is section of adding Channels so here is my code
const [Channels, setChannelState] = React.useState([])
let loadedChannels = []
React.useEffect(() => {
setCurrentUser(props.currentUser)
ref.ref('channels').on('child_added', snap =>{
loadedChannels.push(snap.val())
console.log(loadedChannels)
setChannelState(loadedChannels)
})
},[])
so here when i tried to log the loadedChannels Array am getting all the result but when i try to set this array to my state then am getting only one result
how can i resolve this?
You have an empty array [] as a second parameter which runs the function passed to useEffect only once.
You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect which you have done.
If we pass [] as second parameter this will apply effect only once.
We can pass value inside array, only on change which it will trigger useEffect call.
As per the 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.
try using something like this:
const [Channels, setChannelState] = React.useState([])
React.useEffect(() => {
let loadedChannels = []
setCurrentUser(props.currentUser)
ref.ref('channels').on('child_added', snap =>{
loadedChannels.push(snap.val())
})
setChannelState([...loadedChannels])
},[])
Hope this helps, Happy coding!!!
The second argument for useEffect is called a dependency array. And when it's empty, it acts like componentDidMount() i.e it runs only once, after the first render.
You also need to be careful about setting state in useEffect, since this might create an infinite loop of re-renders if not done correctly - setCurrentUser. That is not enough code to help any further than that.
This is the perfect time to get into React. Have fun!
Second parameter is passed for conditional rendering. To understand that there are few scenarios which tell us the possible ways to use that.
There are multiple scenarios which will make it easier for you to understand the importance and functionality of the second parameter with the help of the example from demo.
Scenario 1
Second parameter as empty array,
useEffect(() => {
console.log(`You clicked ${count} times`);
},
[ ]) //called only once similar to componentDidMount
If we pass an empty array, it means that we are not interested in monitoring any of the values so that the useEffect won’t be called except on mounting and before un-mounting. This mimic’s the working of componentDidMount and componentWillUnmount, it will only run once.
Scenario 2
Second parameter with value(s),
useEffect(() => {
console.log(`You clicked ${count} times`);
}, [count]); //function inside useEffect will be called when "count" will be updated
The value passed as the second parameter (array) here in our example: count will be responsible for the execution of the function inside the useEffect Hook. If the value inside the array will be updated, then and only then the function will be executed.
What if we have multiple useEffect hooks and we need to update just few of them? Conditional rendering comes in light. Conditional rendering is achieved by passing the second parameter instead of just empty array. The values in the array will be monitored and the useEffect function will only be called when the monitored value(s) is/are updated.
Refer these links for more info
https://stackblitz.com/edit/react-hooks-intro
https://www.c-sharpcorner.com/article/react-hooks-introduction/

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