In the DisplayName
component the state keeps updating even though there is no change in state. console.log("Rendered")
and console.log(value)
keeps printing. I can't understand, can someone help me on this.
Below is the code:
import React, { useRef, useState, useEffect, useMemo } from "react";
import './App.css';
import FComponent from "./FComponent";
function App() {
const [name, setName] = useState("am");
const [counter, setCounter] = useState(1);
const result = useMemo(() => {
return factorial(counter)
}, [counter])
console.log("result")
const displayName = () => {
return {name};
}
return (
<div className="App">
<h1>Factorial of {counter} is: {result}
</h1>
<div>
<button onClick={() => setCounter(counter - 1)}>Decrement</button>
<button onClick={() => setCounter(counter + 1)}>Increment</button>
</div>
<hr></hr>
<div>
<div>
<label>Enter Name</label>
</div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
>
</input>
<hr></hr>
<DisplayName displayName={displayName} />
</div>
</div>
);
}
const DisplayName = ({ displayName }) => {
console.log("Rendered")
const [value, setValue] = useState("");
console.log(value);
useEffect(() => {
setValue(displayName());
})
return (<p>{`My name is ${value}`}</p>)
}
function factorial(n) {
if (n < 0) {
return -1
}
if (n === 0) {
return 1
}
return n * factorial(n - 1)
}
function ten() {
return new Date().toString()
}
export default App;
I expected that in DisplayName
component, the state named value
should only update once via the useEffect
from ""
to {"am"}
. Now once it's updated to {"am"}
it should stop updating.
displayName
callback is redeclared each render cycle in the parent component and passed down as props. Since this is a new function reference value it triggers the child component to re-render.useEffect
hook in the child component is missing a dependency array, so the effect callback runs each render cycle and unconditionally enqueues a React state update, which again triggers another component render cycle.displayName
function returns an object instead of a string value. This means that it's a new object reference each time the state update is enqueued and React can't bail out of the state update since the reconciliation process uses shallow reference equality checks. The name
state invariant isn't maintained, i.e. it is defined as a string, it should always be a string. String values are always self-equal, regardless of reference equality.displayName
using the React.useCallback
hook to memoize and provide a stable callback function reference to the child component.displayName
function to return the name
value instead of an object.useEffect
hook to include displayName
as a dependency. Between the useCallback
hook with dependency on name
and the useEffect
hook with a dependency on the displayName
prop the value
state will only update when displayName
is a new callback function reference.function App() {
const [name, setName] = useState("am"); // <-- string type
...
const displayName = useCallback(() => {
return name; // <-- return string type value
}, [name]);
return (
<div className="App">
...
<div>
<div>
<label>Enter Name</label>
</div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<hr />
<DisplayName displayName={displayName} />
</div>
</div>
);
}
const DisplayName = ({ displayName }) => {
const [value, setValue] = useState("");
useEffect(() => {
setValue(displayName());
}, [displayName]); // <-- prop dependency
return <p>My name is {value}</p>;
};
If you simply only want to "seed" the local value
state then omit the displayName
as a dependency. Using an empty dependency will run the effect only once after the initial render cycle.
const DisplayName = ({ displayName }) => {
const [value, setValue] = useState("");
useEffect(() => {
setValue(displayName());
}, []); // <-- empty dependency
return <p>My name is {value}</p>;
};
It's a common general React anti-pattern to store passed prop values into local state, e.g. passing displayName
to copy the name
state value from the parent component to the child components. Update the DisplayName
component to just consume the name value you wish to render directly.
function App() {
const [name, setName] = useState("am");
...
return (
<div className="App">
...
<div>
<div>
<label>Enter Name</label>
</div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<hr />
<DisplayName displayName={name} />
</div>
</div>
);
}
const DisplayName = ({ displayName }) => {
return <p>My name is {displayName}</p>;
};