I've this parent component(Parent
) which holds an inner component(InnerComp
) (for organizing code). The inner component has another nested component(Comp
) which I'm importing from another file. to update Parent
's state from Comp
, I'm passing the setParentCount
function via prop in Comp
function Parent() {
const [parentCount, setParentCount] = useState(0);
const InnerComp = () => (
<>
<h2>necessary inner comp</h2>
<hr />
<Comp setParentCount={setParentCount} />
</>
);
return (
<>
<h1>Parent</h1>
<hr />
<InnerComp />
<p>parent comp count = {parentCount}</p>
</>
);
}
Comp
has its own state as well. the "Click" button in Comp
calls the handleClick
function on click. the handleClick
function is trying to update both the Comp
and Parent
's state. but it seems that compCount
is not getting updated.
function Comp({ setParentCount }) {
const [compCount, setCompCount] = useState(0);
useEffect(() => {
console.log(compCount);
}, [compCount]);
function handleClick() {
setCompCount((prev) => prev 1);
setParentCount((prev) => prev 1);
}
return (
<>
<h3>child comp</h3>
<button onClick={handleClick}>Click</button>
<p>child comp count = {compCount}</p>
</>
);
}
I've added the useEffect
as well for compCount
in Comp
. it's logging every time I click the button. but the same initial value. means the setCompCount
function is setting the old value every time. I wonder why it is happening.
When I add the InnerComp
's JSX directly inside Parent
instead of making a new inner component, it works fine. But I kinda need the InnerComp
to keep my code organized.
I know I can make it work with useContext
, but I think having context here will make this tiny component really heavy.
Here's a codesandbox
CodePudding user response:
The problem is the InnerComp
is getting renewed everytime the parent state updates. Updating the parent state triggers rerender of the parent component in which InnerComp
is declared and rendered. That causes InnerComp
and also Comp
which is in InnerComp
to be initialized everytime the parent state change. It is not a good way to declare a component inside a compoent.
To resolve this, you can move InnerComp
component declaration outside of the Parent
and pass the necessary props. If you don't like passing the setParentCount
props multiple times to child component you can consider using react context or state management libraries such as Redux, Recoil, etc.
const InnerComp = ({setParentCount}) => (
<>
<h2>necessary inner comp</h2>
<hr />
<Comp setParentCount={setParentCount} />
</>
);
function Parent() {
const [parentCount, setParentCount] = useState(0);
return (
<>
<h1>Parent</h1>
<hr />
<InnerComp setParentCount={setParentCount} />
<p>parent comp count = {parentCount}</p>
</>
);
}
CodePudding user response:
Your issue is that you are defining InnerComp
within the Parent
component. This means that every time your Parent
component rerenders, it redefines the InnerComp
function, creating a new component type each time. In other words, on every rerender of Parent
, InnerComp
represents a completely new component, and using it will replace the previous version of InnerComp
and start with the default state.
To fix this, you should move the definition of InnerComp
outside the Parent
component, and make any dependencies of Parent
passed in as props to InnerComp
like so:
const InnerComp = ({ setParentCount }) => (
<>
<h2>necessary inner comp</h2>
<hr />
<Comp setParentCount={setParentCount} />
</>
);
function Parent() {
const [parentCount, setParentCount] = useState(0);
return (
<>
<h1>Parent</h1>
<hr />
<InnerComp setParentCount={setParentCount} />
<p>parent comp count = {parentCount}</p>
</>
);
}
This way, InnerComp
doesn't keep getting redefined and represents the same component type each rerender, thus preserving its internal state.
Here's a CodeSanbox with a working example.