I'm attempting to add a child to my react app via button and have it's value get updated via button click. When I click the button the 'hard coded' child gets updated but the child added via button isn't being updated. I'm missing something fundamental.
I tried passing props to the child assuming state updates would propagate but it isn't working. https://codepen.io/esoteric43/pen/YzLqQOb
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "Starting",
children: []
};
}
handleClick() {
this.setState({ text: "Boom" });
}
handleAdd() {
this.setState({
children: this.state.children.concat(
<Child1 key={1} text={this.state.text}></Child1>
)
});
}
render() {
return (
<div>
<Child1 text={this.state.text}></Child1>
{this.state.children}
<button onClick={() => this.handleClick()}>Change</button>
<button onClick={() => this.handleAdd()}>Add</button>
</div>
);
}
}
class Child1 extends React.Component {
render() {
return <div>{this.props.text}</div>;
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<App />
);
To reproduce in the above codepen link, click add then click change. The added element is not updated. Both children should be updated when the change button is clicked.
CodePudding user response:
You have to map
the previous state values as well in handleClick
method:
handleClick() {
this.setState((prevState) => ({
...prevState,
children:
prevState.children.length > 1
? [
...prevState.children
.slice(0, prevState.children.length - 1)
.map((_) => "Boom"),
"Boom"
]
: ["Boom"]
}));
}
Check it working on this CodeSandbox.
CodePudding user response:
You need to update the last item in children
. Your current solution updates the initial text
.
Try like this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "Starting",
children: ["Starting"]
};
}
handleClick() {
this.setState((prevState) => ({
...prevState,
children:
prevState.children.length > 1
? [
...prevState.children.slice(0, prevState.children.length - 1),
"Boom"
]
: ["Boom"]
}));
}
handleAdd() {
this.setState({
children: this.state.children.concat(
<Child1 key={1} text={this.state.text}></Child1>
)
});
}
render() {
return (
<div>
{this.state.children.map((child, index) => (
<div key={index}>{child}</div>
))}
<button onClick={() => this.handleClick()}>Change</button>
<button onClick={() => this.handleAdd()}>Add</button>
</div>
);
}
}
class Child1 extends React.Component {
render() {
return <div>{this.props.text}</div>;
}
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>
CodePudding user response:
Do you mean something like this?
class App extends React.Component {
constructor() {
super()
this.state = {
children: []
}
}
handleAddClick = ()=>{
this.setState(prev=>({children:[...prev.children, `child ${this.state.children.length 1}`]}))
}
handleChangeClick=(item)=> {
let draft = [...this.state.children];
draft[draft.findIndex(i=>i===item)]='boom';
this.setState({children: draft});
}
render() {
return(
<div>
<button onClick={this.handleAddClick}>Add child</button>
{
this.state.children.map(child=>(<Child onChange={this.handleChangeClick} title={child}/>))
}
</div>
)
}
}
class Child extends React.Component {
render() {
return <div>
{this.props.title}
<button onClick={()=>this.props.onChange(this.props.title)}>change me</button>
</div>
}
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>