React (hooks) different value for refs on initial render - javascript

Can someone explain to me what is the reason for this situation!
Let's say I have a function which generates a random value on initial render. Then I assign the result using ref to keep the value unchanged after subsequent component updates.
After that I assign the ref value to some state. The state is initialized with the ref value inside setTimeout.
import React, { useRef, useState } from "react";
import value from "./example";
function App(){
const v = useRef(word());
console.log(v.current); // Here for example the value is "test"
setTimeout(() => {
console.log(v.current); // Here the value is different
}, 1000)
const [state, setState] = useState(v.current);
return (
<div>{v.current}</div>
)
}
I use setTimeout to illustrate the case but even without it the result is the same the state will be crated with different word although there is no component update.
How many times the component is render for the first initialization although there is no state change?

You are running your app in strict mode. Go to index.js and comment out the strict mode tag. You will find a single render.
This only applies to development mode. Lifecycles will not be double-invoked in production mode.
This is done by intentionally double-invoking the following functions:
https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects

Related

Assigning value at useState() declaration vs useEffect()

I have been using these two ways interchangeably however I am not sure which one is the more correct. their behavior seems to be the same but I am sure there is a use case for each. Anyone can help me understand what's the proper use for each case?
const [customer, setCustomers] = useState(props.location.state);
useEffect(() => {
setCustomers(props.location.state);
}, []);
You should normally stick to the first one. Calling the setter of useState may lead to undesired re-renders and decreased performance.
In the first block the customer is initialised directly and no re-render happens. The setCustomer method will change the state and rerender the component. In the end the whole function will run twice which you can verify with a console.log.
const [customer, setCustomers] = useState(0);
useEffect(() => {
setCustomers(15);
}, []);
console.log(customer) // will first output 0 and then 15
Assuming in the second case, you have this as your useState statement:
const [customer, setCustomers] = useState();
The second one sets the value of customer on componentDidMount. So in the initial render, you will not have the appropriate value in your customer variable.
But yes, very soon after that the correct value will be set because of the code written in useEffect.
To clear it up, there will be 2 renders here (because the state variable value changes). In the first one, that won't be the case since the state variable has only one value from beginning.
The first one is more effective.
const [customer, setCustomers] = useState(props.location.state);
If you use second one (by using useEffect), your component will be re-rendered again.
That's, your state variable customer will be updated in useEffect after DOM is initially rendered, this leads the 2nd re-render of the component.
But if you want customer to be updated by props.location.state, you need to add useEffect hook like the following.
useEffect(()=> {
setCustomers(props.location.state);
}, [props.location.state]);
Setting the state's default value upon declaring it is probably the more correct way to go, since it does not trigger a re-render.
Every time you call a setState your component will be re-rendered, so when you do so in the useEffect, you will trigger an unnecessary re-render upon the component mounting, which could be avoided by doing the good ol'
const [value, setValue] = useState(props.location.state)
While of course there are exceptions and many different use cases, setting an initial state in a useEffect is more useful, for example, when you have values you'd expect to change regardless of your component (for example from an external asynchronous API call):
const [value, setValue] = useState(valueExpectedToChange)
useEffect(() => {
setValue(valueExpectedToChange) // will trigger the rerender only when valueExpectedToChange changes
}, [valueExpectedToChange])

In react when state changes, are useState and useEffect also updated?

When the state changes, to what extent the component is updated? Let's say, when state A changes, I can read the console log of render in Component. I'm wondering what happens to the statement useState since the initial value is set at 1 because the initial value should not be ignored. When I call someFunction, a now becomes 2, but if rerendering occurs, what does happen to const [a,setA] = useState(1)?
For useEffect, when state A changes, I also think useEffect is re-claimed (and for sure, dependency has changed!), but what happen to the previously stated version of useEffect?
Whenever I click the button, new version of useState and useEffect are generated, and what happen to the old versions of these pairs? Are they being stored into some kind of memory of browsers? Judging from the react debugger, we can navigate to the previous look with the previous state values, which means the snapshots of the states are somehow stored. I am super curious where they are! If that's true, when certain amount state changes exceeds the memory limit, would our app be in crisis?
Looking forward to getting any feedbacks about this question!
const Component = () => {
console.log('render component');
const [a, setA] = useState(1);
const someFunction = () => {
console.log('some function')
setA(prev=>prev+1)
}
useEffect(() => {
console.log('use effect')
console.log(a);
}, [a])
return <>
<div onClick={someFunction}>Click</div>
</>
}
what happens to the statement useState since the initial value is set at 1 because the initial value should not be ignored
When a component first mounts, any useStates used inside it create a value mapped to that state in React's internals. The initial value for that is the argument passed to useState. It may be like an array. For example, if you have
const [a, setA] = useState(1);
const [b, setB] = useState(5);
Then, once the component renders, React is storing internally something like
[1, 5]
corresponding to the first and second states.
When you set state, the corresponding value in React's internals changes. For example, if you ran, once:
setA(prev=>prev+1)
React would then have
[2, 5]
And then the component would re-render. On re-renders, the initial state (passed to useState) is ignored - instead, the stateful value is taken from the value in React internals. So with
const [a, setA] = useState(1);
const [b, setB] = useState(5);
on re-render, a evaluates to 2, and b evaluates to 5.
what happen to the old versions of these pairs?
The old values may get garbage collected if nothing can reference them anymore. (The state setter functions are stable.)
For useEffect, when state A changes, I also think useEffect is re-claimed (and for sure, dependency has changed!), but what happen to the previously stated version of useEffect?
Yes, with a new render, prior useEffect callback functions will eventually get garbage collected, with React replacing them internally with the new effect callback function(s) for the current render.
when certain amount state changes exceeds the memory limit, would our app be in crisis?
No, because unused objects from prior renders will get garbage collected.
The following snippet will log Just got GC'd! when enough objects have piled up in memory and they get garbage collected:
const r = new FinalizationRegistry(() => {
console.log('Just got GCd!');
});
const App = () => {
const [someObj, setSomeObj] = React.useState({ num: 0 });
setTimeout(() => {
r.register(someObj);
setSomeObj({ num: someObj.num + 1 });
}, 100);
return JSON.stringify(someObj);;
};
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
The argument passed to useState is the initial state much like setting
state in constructor for a class component and isn't used to update
the state on re-render
If you want to update state on prop change, make use of useEffect hook
React.useState does not reload state from props

Component rendering multiple times without any reason

I was facing multiple re-renders and thus to find the issue I commented every line of code and at last found the constant definition causing re-render:
const MyComponent = () => {
console.log('render') // logs 4 times
const myRef = useRef(null)
return <h1>hello</h1>
}
const MyComponent = () => {
console.log('render') // logs 2 times
return <h1>hello</h1>
}
I know strict mode renders the component 2 times. But, why just defining myRef constant causing re-render further 2 times?
I just tried React.memo as comment, but it still renders 4 times:
export default React.memo(MyComponent)
Okay, I just tested using the same code in App.js and it only logged 2 times. So, it seems to be parent component issue. But my question is why in the child component without having anything causing renders additional 2 times just defining the variable?
As you and React docs said:
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
Source: https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
It does not include useRef but it actually should be on this list because it uses same underlying mechanism as useState
React.memo won't help here because what causes re-render is React.StrictMode special mechanism to check side effects and other stuff, so it ignores memo and similar techniques.
What's interesting here that if you add useRef in Parent component it will invoke function body 2 times, but its children (if they don't use useRef) are gonna be invoked only once.
All this stuff only works for development mode, so in production there won't be any intentional double invocations and other things but they can still happen unintentionally.

Does calling setter of useState hook inside if statement imply BREAKING RULES OF HOOKS?

React docs state: don’t call Hooks inside loops, conditions, or nested functions.
Does calling a hook means just calling useState e.g. const [state, useState] = useState(0)?
What about calling setter in conditionals ?
Is this code breaking rules of hooks ?
const [oneHook, setOneHook] = useState(0)
const [anotherHook, setAnotherHook] = useState(false)
if (something) {
setOneHook(1)
setAnotherHook(true)
} else {
setOneHook(0);
setAnotherHook(false)
}
Thanks !
No, that code example does not break the rules of hooks. Every time the component renders there will be exactly the same number of calls to useState in exactly the same order, so that will be fine.
I do want to point out that setting state right away in the body of the component doesn't make much sense. When the component mounts it will start rendering with the initial values from state, but then before it can finish rendering the state has already changed and it has to start over. But presumably that's just an artifact of the example, and in your real code the if would be inside a useEffect or some other practical code location.
React docs state: don’t call Hooks inside loops, conditions, or nested functions.
Alright,the following code shows the example for the above statement. I had the same issue where i needed to set the state inside of a loop, like following
const [someArray, setArray] = useState([]);
someValue.forEach((value, index) => {
setArray([...someArray, value]) //this should be avoided
});
Above thing i have achieved like this following
var temp = [];
var counter = 0;
someValue.forEach((value, index) => {
temp.push(value);
counter++;
if (counter === someValue.length) {
setArray(temp)
}
});
if you are setting a state inside the loop than each time the component re renders which you do not want to get into.
Is this code breaking rules of hooks
No Your code looks fine, as you are setting up the state only based on condition for only once when the component renders

Gatsby: Context update causes infinite render loop

I am trying to update context once a Gatsby page loads.
The way I did it, the context is provided to all pages, and once the page loads the context is updated (done with useEffect to ensure it only happens when the component mounts).
Unfortunately, this causes an infinite render loop (perhaps not in Firefox, but at least in Chrome).
Why does this happen? I mean, the context update means all the components below the provider are re-rendered, but the useEffect should only run once, and thats when the component mounts.
Here is the code: https://codesandbox.io/s/6l3337447n
The infinite loop happens when you go to page two (link at bottom of page one).
What is the solution here, if I want to update the context whenever a page loads?
The correct answer for this issue is not to pass an empty dependency array to useEffect but to wrap your context's mergeData in a useCallback hook. I'm unable to edit your code but you may also need to add dependencies to your useCallback like in my example below
import React, { useState, useCallback } from "react"
const defaultContextValue = {
data: {
// set initial data shape here
menuOpen: false,
},
mergeData: () => {},
}
const Context = React.createContext(defaultContextValue)
const { Provider } = Context
function ContextProviderComponent({ children }) {
const [data, setData] = useState({
...defaultContextValue,
mergeData, // shorthand method name
})
const mergeData = useCallback((newData) {
setData(oldData => ({
...oldData,
data: {
...oldData.data,
...newData,
},
}))
}, [setData])
return <Provider value={data}>{children}</Provider>
}
export { Context as default, ContextProviderComponent }
The selected answer is incorrect because the react docs explicitly say not to omit dependencies that are used within the effect which the current selected answer is suggesting.
If you use es-lint with the eslint-plugin-react-hooks it will tell you this is incorrect.
Note
If you use this optimization, make sure the array includes all values
from the component scope (such as props and state) that change over
time and that are used by the effect. Otherwise, your code will
reference stale values from previous renders. Learn more about how to
deal with functions and what to do when the array changes too often.
https://reactjs.org/docs/hooks-effect.html
Is it safe to omit functions from the list of dependencies? Generally
speaking, no. It’s difficult to remember which props or state are used
by functions outside of the effect. This is why usually you’ll want to
declare functions needed by an effect inside of it. Then it’s easy to
see what values from the component scope that effect depends on:
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
By default, useEffect runs every render. In your example, useEffect updates the context every render, thus trigger an infinite loop.
There's this bit in the React doc:
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.
So applies to your example:
useEffect(() => {
console.log("CONTEXT DATA WHEN PAGE 2 LOADS:", data)
mergeData({
location,
})
- }, [location, mergeData, data])
+ }, [])
This way, useEffect only runs on first mount. I think you can also leave location in there, it will also prevent the infinite loop since useEffect doesn't depend on the value from context.

Categories