Issue with using useLayoutEffect React - javascript

Hook useEffect runs asynchronously and usually after the DOM is rendered/mounted, whereas useLayoutEffect runs synchronously and before the DOM is rendered/mounted.
Using my example useEffect, all works fine and I get desired result (I store the previous value that the user has entered using useRef).
But using my example useLayoutEffect, I don't get the wanted result, cause it just works as useEffect in my case. I reckon that it should firstly update prevName.current = name and only after render causing the same value that person has entered. I think, that in that case both prevName.current and name should be rendered as same values. But that doesn't happen.
Can you please explain why?
Here's codesandbox: Sandbox
Here's the code:
import React, { useLayoutEffect } from "react";
import "./styles.css";
import { useState, useRef, useEffect } from "react";
export default function App() {
const [name, setName] = useState("");
const prevName = useRef(name);
useLayoutEffect(() => {
prevName.current = name;
}, [name]);
return (
<div className="App">
<input
value={name}
onChange={(e) => {
setName(e.target.value);
}}
></input>
<div>Your name is {name}</div>
<div>Your previous name is {prevName.current}</div>
</div>
);
}

Hook useEffect runs asynchronously and usually after the DOM is rendered / mounted, whereas useLayoutEffect runs synchronously and before the DOM is rendered / mounted.
Both of them occur after the changes have been put onto the DOM. What's special about layout effects is that if you set state during the layout effect, the resulting render will be done synchronously. For normal effects, if you set state, it will rerender soon, but not synchronously. If you never set state (as in your example), it's not going to make much difference which one you use.
The most common application of useLayoutEffect is that you want to render once, measure the stuff on the dom, and then render again without the user seeing the temporary stuff you had on the dom.

Related

How to fix the following "Too many re-renders error" in React?

I'm trying to render the string array keys into a React component. keys are the keys that the user presses (but I just hard-coded them for the sake of this example).
import { useState } from "react";
import * as ReactDOM from "react-dom";
let keys = ["a", "b"];
function App() {
let [keysState, setKeysState] = useState([]);
setKeysState((keysState = keys));
return (
<div>
{keysState.map((key) => (
<li>{key}</li>
))}{" "}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
But I'm getting this error:
Too many re-renders. React limits the number of renders to prevent an infinite loop.
I know I can avoid this error by creating and onClick handler ... but I don't want to display keysState on click. I want it to display and re-render immediately when keys changes.
Live code: https://codesandbox.io/s/react-18-with-createroot-forked-vgkeiu?file=/src/index.js:0-504
when the page loads, the setKeysState function gets invoked and it updates the state, updating the state in reactjs causes a re-render and it keeps doing that infinitely. which produces Too many re-renders error.
just pass an initial value to the useState() hook to initialize the state. like this :
let [keysState, setKeysState] = useState(keys);
NOTE : In your case You do not need The React useState Hook because you're not tracking the data (keys in your case, you are not be updating it )
just change your component like this :
let keys = ["a", "b"];
function App() {
return (
<div>
{keys?.map((key) => (
<li>{key}</li>
))}
</div>
);
}
While #monim's answer is great, I feel that you don't need a useState hook if you don't need setKeysState
import * as ReactDOM from "react-dom";
let keys = ["a", "b"];
function App() {
return (
<div>
{keys.map((key) => (
<li>{key}</li>
))}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
EDIT: I put together a series of links that I did not want to lose. If you want to deep dive, here's what I personally saved on the hooks: https://github.com/criszz77/react-js-links#hooks
There seems to be a bit of confusion as to how useState works.
You are getting infinite refreshes, because the component re-renders if there is a change to a state. And what is happening there is, that you component loads, you init your state and set value, which triggers re-render and that runs in loop. To solve it just set initial value to keys instead of []
Things that no one mentioned
Make states constants as there is, I think, never a good reason to change the fact that it's a state and if you want to change the value you use setKeysState
setKeysState is a function where that you call with the new value of keysState, so do never change value of that state by anything else but setKeysState(newValue). You are passing something that I'd describe as ?function?. Just pass the value.
Solution:
const [keysState, setKeysState] = useState(keys);
Many different problems with this code, and this allow me to tell you that you haven't got the way of React hooks yet.
First of all, you don't need to write this setKeyState function. The useState hook will return it when invoked.
Second, if you want to provide an initial value to your keyState, you should do it using the setState hook, just like this:
const [keyState, setKeyState] = useState(["a","b"]);
This would create the keyState variable and initialize it with ["a","b"], as well as provide the setKeyState function.
I believe this is the main problem with this code. This redundance is causing the perpetual re-render.
Finally, you should have some way to react to state changes. This will be given by the useEffect hook.
useEffect(() => {
// Things to perform when `stateKeys` changes
},[stateKeys])
The second parameter this hook receives [stateKeys] is exactly to prevent a perpetual re-rendering. It tells the hook it should run only when the variables inside this array changes.
It seems to me, allow please don't be offended for me to say, that you missed something in React way of doing things. Specially when it comes to React Hooks. I suggest you to read the documentation again:
https://reactjs.org/docs/hooks-state.html

Re-rendering in React functional component - why this behavior?

This question concerns the definition of callback functions inside functional components, with the callback functions being passed down as props to child functional components.
I am aware that when defining functions inside functional components and those functions are being passed on as props to child components we should use useCallback. In this example I am deliberately not using it.
I have a dummy React app as below:
App.jsx
import { useState, useCallback } from 'react';
import './App.css';
import TestComponent from './components/TestComponent';
import TestComponent2 from './components/TestComponent2';
function App() {
const [message, setMessage] = useState("");
console.log('Defining myArrowfunc');
const myArrowFunc = () => console.log('Hello!');
console.log('myArrowfunc defined');
return (
<>
<TestComponent message={message} myArrowFunc={myArrowFunc} />
<TestComponent2 myArrowFunc={myArrowFunc} />
<input
type="text"
value={message}
onChange={e => setMessage(e.target.value)}
/>
<button
type="button"
onClick={() => {
console.log('Setting message');
setMessage("x");
}}>Set to x</button>
</>
);
}
export default App;
TestComponent.jsx
import { useEffect } from 'react';
function TestComponent(props) {
useEffect(() => {
console.log(`TestComponent.useEffect, props.message = ${props.message}`);
}, [props]);
return <h2>TestComponent - {props.message}</h2>;
}
export default TestComponent;
TestComponent2.jsx
import { useEffect } from 'react';
function TestComponent2(props) {
useEffect(() => {
console.log(`TestComponent2.useEffect`);
}, [props.myArrowFunc]);
return <h2>TestComponent2 - Placeholder</h2>;
}
export default TestComponent2;
Starting the app, the page loads and we are here:
In the console window everything looks like expected. The console.log statements Defining myArrowFunc and myArrowFunc defined ran, and we can see the console.log statements from the useEffect hook within TestComponent and TestComponent2 which ran after they were rendered.
Now we click on the button Set to x, which invokes the callback function attached to the onClick event of the button. This callback calls setMessage("x") which updates the state of the App component and consequently triggers a re-render of the component.
In the console window we can see that the App functional component ran (e.g. from the console.log("Defining myArrowFunc)) and one can also see that the useEffect hooks of the child components ran after they were re-rendered.
Now, that TestComponent re-rendered is of course understandable. message state is a prop on TestComponent which would cause a re-render of TestComponent. Also, in the dependency array of TestComponent is specified props (not props.message), but props is a new object on every render (right?) and hence the reference comparison tells us props has changed and so useEffect runs.
That the useEffect hook of TestComponent2 ran can be understood (right?) from the fact that myArrowFunc is re-defined on each render of App, and so the prop passed to TestComponent2 has in fact changed (reference comparison).
Here comes the part where I become confused: the message state in App now holds "x", and so additional clicks to the button will not change the state, and so should not trigger a re-render of App (and any child components dependant). When I click the button the following output happens:
The statements console.log('Defining myArrowfunc'); and console.log('myArrowfunc defined'); ran. What does this mean? Did the component re-render? If the App function ran, then it should have defined a new myArrowFunc (right?), and since TestComponent2 takes that as a prop, it should have re-rendered and run its useEffect hook?
What's interesting as well is that when I again click the button, it does not look like App ran, the output becomes only "Setting message".
Would very much appreciate an outline / explanation of what exactly is going on here.
When you click the button, a console log with setting message will run, and then a setState with will make a re-render.
At the following click, react is clever enough to see that the state you are setting is the same as before so it doesn't re-render
If you change then, the value and click the button, it will re-render, but not for the following cases with the same value.
To sum up, the first's console.log won't run if there's not a re-render, and the procedure of react--if default--memoizing will prevent it

Why setState causes too many rerender Error even though the state hasn't changed

Hi I'm learning React now and having trouble with the state..
I know when the state changes, the component re-renders and the code in UseEffect widout depth only runs once.
But I can't explain exactly why infinite rendering occurs when I write setState in JSX or in the render syntax.
below code causes infinite re-render
import React, { useState, useEffect } from 'react'
const index = () => {
const [active, setActive] = useState(false);
console.log("render", active);
setActive(false);
return (
<div>
</div>
)
}
export default index
But the code below has no problem even though it keeps calling setState.
import React, { useState, useEffect } from 'react'
const index = () => {
const [active, setActive] = useState(false);
console.log("render", active);
useEffect(() => {
setInterval(()=>{
console.log("run")
setActive(true)
},0);
}, [])
return (
<div>
</div>
)
}
Does setState trigger re-rendering regardless of state value?
I want to know the exact reason why using setState outside of useEffect causes an error.
This is happening because, in the first case, when useEffect is not used,
you are updating your state right after declaring it.
Even if you are setting the state as false again, but for react, the state has been updated. And the first rule of thumb for react is, if state update happens, component will re render.
This is why you are getting an infinite rerendering.
your code is following the below flow:
Declare state variable and pass value as false
Update state to false
State updated, hence component re rendered.
Step 1 again.
In second case, where use effect is used, your state will update only when the component is mounted, which means, after that any state update won't trigger your useEffect.
Based on React documentation: https://reactjs.org/docs/hooks-reference.html#usestate
The setState function is used to update the state. It accepts a new state value and enqueues a re-render of the component.
And there is an addition: https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.) Note that React may still need to render that specific component again before bailing out.
And here is the important part: React may still need to render that specific component again before bailing out.
So yes, the component may still rerender even if the values are the same.

Why does react compute render twice before initial load?

I was under the impression that react render computes once and compares it with DOM to make the change?
However, from my simple example: https://codesandbox.io/s/admiring-rain-ej8fu?file=/src/App.js it seems even before the component mounts. The render function is exceuted twice. I've also included a screenshot below to clarify my point. In the console you can see console.log(value) is evaluated twice. I wonder what conceptual mistake I'm making here!
Code:
import React, { useState, useEffect } from "react";
function Load() {
const [value, setValue] = useState("intial Value");
console.log(value);
useEffect(() => {
console.log("mounted");
}, []);
return (
<div>
<h1>{value}</h1>
<button onClick={e => setValue(Math.random())}>Update state</button>
</div>
);
}
export default Load;
Screenshot:
react initial reload error
It is because codesandbox uses React Strict Mode. React Strict Mode is for better debugging, so, after rendering it reruns all the lifecycle methods to make sure they're "safe". You can reead more about it here. So, just remove React.StrictMode tag in index.js file.

Can I declare a variable outside of useEffect ? React Hooks

I'm learning React Hooks, and I'm actually using the useEffect method.
No problem at all, but I got some warnings about my variable declarations.
Here's an example of what I wrote :
import React, { useRef, useEffect } from 'react';
function App(){
let containerRef = useRef(null);
let myVariable;
useEffect(){
myVariable = containerRef.children[0];
}
return(
<div className="container" ref={el => containerRef = el}>
<h1>Hey, I'm Laurie </h1>
<p> Nice to e-meet you!</p>
</div>
)
}
This is just an easy and uncompleted example of what I did, for animating my website with GSAP. I access to the DOM elements with useRef and I only found this solution.
But my console write me some warnings, and I'm pretty lost.
Warning I got :
Assignments to the myVariable variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect.
Can someone help me with this issue ?
when you are using Hook basically you are telling React that your component needs to do something after render. React will remember the function you passed, and call it later after performing the DOM updates.
More Details
https://reactjs.org/docs/hooks-effect.html#example-using-hooks
Regarding warning: React doesn't remember your myVariable. It will be recreated on each render. It will be better to use useState hook to set value in useEffect and remember it on the next render.
Remember one thing always pass the second argument in useEffect with an array of dependencies.

Categories

Resources