I have two components in my project.
One is Aapp.jsx
One is Child.jsx
When I called the state set function in Child 1; it is supposed to see mentioning child 1 in the console, but now it is showing child 3. It is weird and why is that?
I guess the reason is "key" in prop, so I added "key" there too. But the problem is still there.
Here is the code: App:
import React,{useState,useEffect} from 'react';
import {Child} from './Child.jsx'
export function App(props) {
[message,setMessage]=useState('');
[showChild1,setShowChild1]=useState(true);
[showChild2,setShowChild2]=useState(true);
[showChild3,setShowChild3]=useState(true);
[child1data,setChild1data] = useState('child1');
[child2data,setChild2data] = useState('child2');
[child3data,setChild3data] = useState('child3');
useEffect(() => {
console.log('parent was rendered')
})
return (
<div className='App'>
<button onClick={()=>setShowChild1(!showChild1)}>Show child1</button>
{showChild1 && <Child key='1' data={child1data}/>}
<br/>
<br/>
<button onClick={()=>setShowChild2(!showChild2)}>Show child2</button>
{showChild2 && <Child key='2'data={child2data}/>}
<br/>
<br/>
<button onClick={()=>setShowChild3(!showChild3)}>Show child3</button>
<br/>
{showChild3 && <Child key='3' data={child3data}/>}
</div>
);
}
// Log to console
console.log('Hello console')
Child:
import React, {useState, useEffect} from 'react';
export const Child = (props) => {
const {data} = props;
[message,setMessage]=useState('');
useEffect(()=>{
console.log(data)
console.log(message)
})
return <>
<h1>This is {data}</h1>
<input onChange={((e)=>setMessage(e.target.value))}></input>
</>
}
For better illustrate, here is my code https://playcode.io/940717
CodePudding user response:
This is a tricky one but fortunately comes with a very simple fix.
TL;DR
In Child.jsx
change this:
[message, setMessage] = useState('');
to this:
const [message, setMessage] = useState('');
The longer answer
When declaring variables without let
, const
, or var
(in a non-strict environment) Javascript will create an implicit global variable. What this means is that your message
and setMessage
variables point to the last value they were assigned. I.e. The global variable message
will be assigned to the result of the useState
call in the last Child
component that you render, which in your case is Child 3
.
So, when you modify message
, Child 3
detects that change and runs the effect for Child 3
, not the Child
component where the change was actually made.
You can see this in action by changing the Child
component to this and examining the output in the console:
[message, setMessage] = useState("");
useEffect(() => {
console.log(data); // Will always log "child 3"
console.log(message);
});
return (
<>
<h1>This is {data}</h1>
<input
onChange={(e) => {
console.log(data); // Will log "child n" where n is the child you expect
setMessage(e.target.value);
}}
></input>
</>
);
You might also be curious as to why, if all of your Child
components are referencing the same message
variable, you're still able to change the inputs individually. This is because you're input
elements are uncontrolled and their state is being managed by the browser natively.
You can see this in action by adding the following to the input
element in your Child
component:
<input value={message} onChange={e => setMessage(e.target.value)}></input>
What to do in the future
You should always, always, always declare your variables with let
or const
as it will save you headaches like this in the future. In fact, many modern transpilers such as Babel will throw an error if you don't.
When using uncontrolled inputs you should consider asking yourself why you need to do so. More often than not, a controlled input is what you actually want and in the cases where it's not, consider leaving a comment as to why it's not.
CodePudding user response:
You forgot const
in many places:
const [message, setMessage] = useState('');
Without the const
keyword message
and setMessage
(and other stuff) leak to the window global object.