As you can see in below screencast, manipulating the second input field in my form also changes the value of the first one. I've completely reproduced it on JSfiddle in an isolated environment to track down the bug but didn't find it.
Quick overview of my code base:
A functional component ModuleBuilder has an Array in its state called blocks
and a renderFunction which renders for each element of blocks
one instance of a subcomponent called ModuleElement.
The subcomponent ModuleElement displays an input form element. When I create n instances of the subcomponent and try to type something in the the form element of the second instance then the first one's value gets updated as well.
const ModuleElement = (props) => {
const blockElement = props.config;
const key = props.index;
const changeValue = (e) => {
blockElement[e.target.name] = e.target.value;
props.updateBlock(key, blockElement);
};
return (
<div key={`block_${key}`}>
<input
key={`block_input_${key}`}
type="text"
id={`fieldname_${key}`}
name="fieldName"
onChange={changeValue}
placeholder="Field Name"
defaultValue={blockElement.fieldName}
/>
</div>
);
};
const ModuleBuilder = () => {
const emptyBlock = {
fieldName: "",
required: true,
};
const [blocks, setBlocks] = React.useState([emptyBlock]);
const updateBlock = (key, data) => {
//console.log(`i was asked to update element #${key}`);
//console.log("this is data", data);
let copyOfBlocks = blocks;
//copyOfBlocks[key] = data; // WHY DOES COMMENTING THIS OUT STILL MAKE THE UPDATE WORK??
setBlocks([...copyOfBlocks]);
// console.log("this is blocks", blocks);
};
const add1Block = () => {
setBlocks([...blocks, emptyBlock]);
};
const renderBlockFormElements = () => {
return blocks.map((value, index) => {
return (
<li key={index}>
<b>Subcomponent with Index #{index}</b>
<ModuleElement
index={index}
config={value}
updateBlock={updateBlock}
/>
</li>
);
});
};
return (
<div>
<h1>
Click twice on "add another field" then enter something into the second
field.
</h1>
<h2>Why is 1st field affected??</h2>
<form>
<ul className="list-group">{renderBlockFormElements()}</ul>
<button type="button" onClick={add1Block}>
Add another field
</button>
</form>
<br></br>
<h2>This is the state:</h2>
{blocks.map((value, index) => (
<p key={`checkup_${index}`}>
<span>{index}: </span>
<span>{JSON.stringify(value)}</span>
</p>
))}
</div>
);
};
ReactDOM.render(<ModuleBuilder />, document.querySelector("#app"));
see full code on https://jsfiddle.net/g8yc39Lv/3/
UPDATE 1:
- I solved the problem (see my own answer below) but am still confused about one thing: Why is
copyOfBlocks[key] = data;
in theupdateBlocks()
not necessary to update the state correctly? Could it be that the following code is manipulating the props directly??
const changeValue = (e) => {
blockElement[e.target.name] = e.target.value;
props.updateBlock(key, blockElement);
};
If yes, what would be the react way to structure my use case?
UPDATE 2:
It turns out indeed that I was manipulating the props directly. I changed now the whole setup as follows.
The Module Element component now has its own state. OnChange the state is updated and the new target.value is pushed to the props.updateblocks method. See updated JSfiddle here. Is this the react way to do it ?