Reputation: 176
In the code below when the checkbox is checked in AddressWrapper the Ship To input in the AddressForm should be disabled. I can not figure out why AddressWrapper cloneElement is not passing it's state to the child. I have checked out many links about this issue and as far as I can tell this should work. This is the closest How to pass props to {this.props.children} to this problem but it is using a callback from the child to the parent and I need a change in parent state to update the child. I could use a publish/subscribe to do it but I'm trying to do it the 'React' way.
class AddressForm extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: "Joyce",
disableInputs: props.billToSameAsShipTo
};
this.handleBillToSameAsShipToChanged = this.handleBillToSameAsShipToChanged.bind(
this
);
}
handleBillToSameAsShipToChanged() {
this.setState({ billToSameAsShipTo: !this.state.billToSameAsShipTo });
}
handleFirstNameChanged(ev) {
this.setState({ firstName: ev.target.value });
}
render() {
return (
<form>
<div className="form-row">
<div className="col-6">
<input
type="text"
className="form-control"
placeholder="First name"
disabled={this.state.disableInputs}
value={this.state.firstName}
onChange={this.handleFirstNameChanged.bind(this)}
/>
</div>
</div>
</form>
);
}
}
class AddressFormWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
billToSameAsShipTo: true
};
this.handlebillToSameAsShipToChanged = this.handlebillToSameAsShipToChanged.bind(
this
);
}
handlebillToSameAsShipToChanged() {
this.setState({ billToSameAsShipTo: !this.state.billToSameAsShipTo });
}
render() {
const billToSameAsShipTo = () => {
if (this.props.showSameAsShipTo === true) {
return (
<span style={{ fontSize: "10pt", marginLeft: "20px" }}>
<input
type="checkbox"
checked={this.state.billToSameAsShipTo}
onChange={this.handlebillToSameAsShipToChanged}
/>
<span>Same as Ship To</span>
</span>
);
}
};
const childWithProp = React.Children.map(this.props.children, child => {
return React.cloneElement(child, { ...this.state });
});
return (
<span className="col-6">
<h3>
{this.props.title}
{billToSameAsShipTo()}
</h3>
<span>{childWithProp}</span>
</span>
);
}
}
const Checkout = () => {
return (
<div>
<br />
<br />
<div className="row">
<AddressFormWrapper title="Ship To" showSameAsShipTo={false}>
<span className="col-6">
<AddressForm />
</span>
</AddressFormWrapper>
<AddressFormWrapper title="Bill To" showSameAsShipTo={true}>
<span className="col-6">
<AddressForm />
</span>
</AddressFormWrapper>
</div>
</div>
);
};
Upvotes: 1
Views: 75
Reputation: 31024
In AddressFormWrapper
you map over the children and passing props with cloneElement()
.
As per the DOCS:
Invokes a function on every immediate child contained within children...
But take a good look who are those (immediate) children of AddressFormWrapper
:
<AddressFormWrapper title="Bill To" showSameAsShipTo={true}>
<span className="col-6">
<AddressForm />
</span>
</AddressFormWrapper>
In this case its the span
element and not AddressForm
.
If you render it like this it will work as expected:
<AddressFormWrapper title="Bill To" showSameAsShipTo={true}>
<AddressForm />
</AddressFormWrapper>
Another thing to watch out from, in AddressForm
you are setting the state:
disableInputs: props.billToSameAsShipTo
This is inside the constructor
and it will only run once. So it will get the initial value but won't get changed.
Either update it in componentDidUpdate or better just use the props directly:
disabled={this.props.billToSameAsShipTo}
Here is a running example:
class AddressForm extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: "Joyce",
disableInputs: props.billToSameAsShipTo
};
this.handleBillToSameAsShipToChanged = this.handleBillToSameAsShipToChanged.bind(
this
);
}
handleBillToSameAsShipToChanged() {
this.setState({ billToSameAsShipTo: !this.state.billToSameAsShipTo });
}
handleFirstNameChanged(ev) {
this.setState({ firstName: ev.target.value });
}
billToSameAsShipTo() {
if (this.props.showSameAsShipTo === true) {
return (
<span style={{ fontSize: "10pt" }}>
<input
type="checkbox"
checked={this.state.billToSameAsShipTo}
onChange={this.handleBillToSameAsShipToChanged}
/> <span>Same as Ship To</span>
</span>
);
}
}
render() {
return (
<form>
<div className="form-row">
<div className="col-6">
<input
type="text"
className="form-control"
placeholder="First name"
disabled={this.props.billToSameAsShipTo}
value={this.state.firstName}
onChange={this.handleFirstNameChanged.bind(this)}
/>
</div>
</div>
</form>
);
}
}
class AddressFormWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
billToSameAsShipTo: true
};
this.handlebillToSameAsShipToChanged = this.handlebillToSameAsShipToChanged.bind(
this
);
}
handlebillToSameAsShipToChanged() {
this.setState({ billToSameAsShipTo: !this.state.billToSameAsShipTo });
}
render() {
const billToSameAsShipTo = () => {
if (this.props.showSameAsShipTo === true) {
return (
<span style={{ fontSize: "10pt", marginLeft: "20px" }}>
<input
type="checkbox"
checked={this.state.billToSameAsShipTo}
onChange={this.handlebillToSameAsShipToChanged}
/> <span>Same as Ship To</span>
</span>
);
}
};
const childWithProp = React.Children.map(this.props.children, child => {
return React.cloneElement(child, { ...this.state });
});
return (
<span className="col-6">
<h3>
{this.props.title}
{billToSameAsShipTo()}
</h3>
<span>{childWithProp}</span>
</span>
);
}
}
const Checkout = () => {
return (
<div>
<br />
<br />
<div className="row">
<AddressFormWrapper title="Ship To" showSameAsShipTo={false}>
<span className="col-6">
<AddressForm />
</span>
</AddressFormWrapper>
<AddressFormWrapper title="Bill To" showSameAsShipTo={true}>
<AddressForm />
</AddressFormWrapper>
</div>
</div>
);
};
ReactDOM.render(<Checkout />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"/>
Upvotes: 1