Can I declare a variable outside of useEffect ? React Hooks - javascript

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.

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

Issue with using useLayoutEffect React

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.

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.

Passing a hook as a prop

Is it acceptable / common to pass in a React Hook as a prop to another Component?
Or are they meant to only belong within the component it is declared in?
The following is just an example to illustrate what I mean by passing it as
a prop from the Parent Component to the Child parent.
import React, { useState } from 'react';
export const Parent = () => {
const [count, setCount] = useState(0);
return <div>
Current count is {count}
<Child setCount={setCount} /> // passing a hook set call as a prop to child
</div>
}
const Child = ({setCount}) => {
setCount(10) // using the hook set call
return (
<div>
Some Child Text
</div>
);
};
export default Child;
setCount is not a hook, it is just another function, So you can pass it as a prop. You can also think of the output of useMemo or useCallback, these can be passed as a prop.
useState is a hook, useCallback is a hook, or even for a matter any function, which encapsulates other hooks, these should not be passed as prop.
Why?
To answer this first think of why would you want to pass the hook as a prop? The benefit of passing the hook as a prop will be that you can conditionally pass another hook / skip the call.
But in the case of hooks, It should not be called conditionally. So there is no point. of passing it as a prop, Just import it and use it.
Your example is totally fine and can be used. setCount is just a function. From your example, the state belongs to the parent component. However, its value will come from another component API.
The same would happen if you change the child component in your example for a regular <button onClick={} />.
I know that this example probably doesn't match your real case implementation. I'd only suggest you pay attention to how the child component API is designed in order to make the parent not tightly coupled to the child.

Why doesn't useState function initialize state every time?

import React, { useState } from "react";
function HookCounter() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count {count}</button>
</div>
);
}
export default HookCounter;
React calls this function every time when it needs to re-render.
But why doesn't it initialize the state every time?
When exit the function, life of variables is ended, isn't it?
But how does it keep saving values of states?
I don't understand.
In useState function, is there any logic for that?
useState as a function is storing the value you gave it within React's core. When the component re-renders, React is going to pass the updated count from its core to the component.
More information here.
State is initialized only once when you create the component, this is how React works. From the documentation:
What does calling useState do? It declares a “state variable”. Normally, variables “disappear” when the function exits but state variables are preserved by React.
Just to have the context here, let me summarize what is useState and how it works in more details.
What is useState:
So useState is a hook which helps you to handle state in a functional component.
How is it working?
Once you call useState you need to pass the initial value of the state what you would like to use in your functional component. It returns a pair of values the count and setCount.
So let's have your example below:
const [count, setCount] = useState(0);
So useState returned two items where count is the current value. And the second item, setCount is a function which can be used to update the value of the count state.
count can be used to represent the state for example the value in a div element:
return (
<div>{count}</div>
)
In order to update the value you can achieve by calling setState(12).
From the docs you can read further here.
According to the React official website:
React keeps track of the currently rendering component. Thanks to the Rules of Hooks, we know that Hooks are only called from React components (or custom Hooks — which are also only called from React 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.
Reference:
How does React associate Hook calls with components?
Why doesn't useState function initialize State every time?
I think you're confused with:
const [count, setCount] = useState(0);
As it's just a variable!?!
Yes, it's just a variable but it will take everytime the function runs from the useState hook. And it is the local state like you have seen in class based component.

Categories

Resources