I’ve encountered quite a weird behaviour in ReactJS and it looks like I’m able to update a useState without using a useState setter. First thing first, my useState is declared in the root component and it holds all the data my tool needs.
const RootComponent = () => {
...
const [data, setData] = useState(extractData(rawData))
return(
...
//child components are declared here, here's an example
<ChildComponent
data={data}
setData={setData}
>
);
}
The child components often take input from the user and for that reason I pass down setData
to them so they can update the useState. The structure of my tool is pretty complex and I often render react components using the map
function. To avoid having to access all the nested properties of the useState object in every component, I pass down the specific data needed by that component just like this:
const LeafComponents = ({data, setData}) => {
return(
<Fragment>
{specificData.map((specificData) => (
<Fragment key={specificData.id}>
<LeafComponent
specificData={specificData} />
</Fragment>
))}
</Fragment>
);
}
This is where the weird thing happens. In LeafComponent
I have a textbox linked to a useState textContent
. Everytime the user types in the textbox, the useState is updated AND everytime textContent
changes, specificData’s text property is given a new value.
const LeafComponent = ({specificData}) => {
const [textContent, setTextContent] = useState("")
const handleTextContent = (event) => {
setTextContent(event.target.value)
}
useEffect(()=> {
specificData.text = textContent
}, [textContent, specificData])
return(
<textarea className='largeTextBox' id='kmdGuidanceTextBox'
value={textContent}
onChange={handleTextContent}>
</textarea>
);
}
specificData is “part of” a useState and the assignment you see above shouldn’t update it because setData
wasn’t used. To check if that was the case I debugged this way.
const RootComponent = () => {
const [data, setData] = useState(extractData(rawData))
useEffect(()=> {
console.log(data)
}, [data])
function handleButtonPress() {
console.log(data)
}
return(
...
<button onClick={handleButtonPress}/>
...
)
The console.log in the useEffect
is never activated but the console.log in the handleButtonPress
returns the value of data containing the updated value of specifcData.textbox
. How is that possible if a setter wasn’t used? Why is the useEffect never activated?