javascript React JS this.props.data 未在 getInitialState 中为子组件定义,即使它在渲染中定义(并且在父组件中定义)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28996313/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
React JS this.props.data not defined in getInitialState for child component even though it is defined in render (and is defined in parent)
提问by Ted
Scenario:
设想:
- Re-using components (
<Row />
-><Cell />
) in the same grandparent wrapper (<ShippingTable />
-><Grid />
) for code re-use. - For one, I assign an array of data and loop through to re-use child component (
<Row />
-><Cell />
). - For the second one, it's just an object (with the same properties as the array's objects) which I just assign directly in render (no need for
this.props.data.map
loop as already just one object).
- 在同一个祖父包装器 ( -> ) 中重用组件 (
<Row />
-><Cell />
) 以进行代码重用。<ShippingTable />
<Grid />
- 首先,我分配了一个数据数组并循环遍历以重用子组件 (
<Row />
-><Cell />
)。 - 对于第二个,它只是一个对象(具有与数组对象相同的属性),我只是在渲染中直接分配(不需要
this.props.data.map
循环,因为已经只有一个对象)。
Problem:
问题:
- For the array, all works as required.
this.props.data
is passed down to children, state is updated through various events and all is well. - However, for the single object, all works fine until
<Row />
. Even though this.props.data contains valid values and is properly assigned to the child<Cell />
component, in<Cell />
's getInitialState, it is inexplicably undefined (or set to whatever initial values set in<ShippingTable />
'sgetInitialState
). even though, apart from being called in a .map loop, it is exactly the same code and data that works in the same rendering for my array of data. - Moreover,
this.props.data
in<Cell />
's render is indeed present and accurate, but because of the failed setState in getInitialState, this.state.data is undefined (or set to whatever values set in<ShippingTable />
'sgetInitialState
). - Double moreover, if I force a UI re-render (e.g. change another state property in the grandparent
<ShippingTable />
), everything works as how I would expect it in the first place.
- 对于阵列,一切都按要求工作。
this.props.data
传递给孩子,状态通过各种事件更新,一切都很好。 - 但是,对于单个对象,一切正常,直到
<Row />
. 尽管 this.props.data 包含有效值并正确分配给子<Cell />
组件,但在<Cell />
's getInitialState 中,它莫名其妙地未定义(或设置为<ShippingTable />
's 中设置的任何初始值getInitialState
)。尽管如此,除了在 .map 循环中被调用之外,它是完全相同的代码和数据,它们在我的 data 数组的相同呈现中工作。 - 此外,
this.props.data
in<Cell />
的渲染确实存在且准确,但由于 getInitialState 中的 setState 失败, this.state.data 未定义(或设置为<ShippingTable />
's 中设置的任何值getInitialState
)。 - 此外,如果我强制 UI 重新渲染(例如更改祖父母中的另一个状态属性
<ShippingTable />
),一切都会按照我最初的预期进行。
It's as if getInitialState
in <Cell />
for only my single percentages object is being called before a successful AJAX population of setState, or it's not being called again when the state is changed after being populated with updated data from the server.
就好像getInitialState
在<Cell />
成功的 AJAX 填充 setState 之前只调用了我的单个百分比对象,或者在使用来自服务器的更新数据填充后更改状态时不会再次调用它。
Here's a stripped down (but still long) version of the actual code:
这是实际代码的精简版(但仍然很长):
UPDATE - Solved
更新 - 已解决
Never forget that with React, always send props down, send events up, and always keep state as far up to the top as possible. Removed <Cell />
and <Row />
's state-tracking responsibilities by moving them back up to the ShippingTable (instead referencing props passed down by parent) where state is actually tracked (and should always be tracked). As per @rallrall below, I got off track and was inexplicably working against the framework. All very clear in retrospect (although why the incorrect approach worked for the array but not the object did muddy the waters - especially when switching the object to an array ended up working as well).
永远不要忘记在 React 中,始终向下发送 props,向上发送事件,并始终将状态尽可能保持在顶部。删除<Cell />
和<Row />
的状态跟踪移动它们备份到ShippingTable(而不是引用由家长传下来的道具),其中状态实际上是追踪(并应始终被跟踪)的职责。根据下面的@rallrall,我偏离了轨道,并且莫名其妙地针对该框架工作。回想起来,一切都非常清楚(尽管为什么不正确的方法对数组有效,但对象却没有弄混——尤其是当将对象切换到数组最终也能正常工作时)。
var ShippingInput = React.createClass({
getInitialState: function() {
return { value: this.props.value };
},
handleChange: function(event) {
var value = event.target.value;
...conversion to decimal and validation
this.props.onChange(value);
this.setState({ value: value });
},
render: function() {
return (
<Input type="text" placeholder="0.00" bsStyle={this.validationState()} addonBefore={addOnBefore}
value={this.state.value} label={this.props.label} ref="input" onChange={this.handleChange}
groupClassName={this.props.groupClassName} labelClassName={this.props.labelClassName} onKeyDown={this.props.onKeyDown} />
);
}
});
var Cell = React.createClass({
propTypes: {
data: React.PropTypes.number.isRequired,
onChange: React.PropTypes.func.isRequired,
onRowEdit: React.PropTypes.func.isRequired
},
getInitialState: function() {
// At this point, this.props.data is undefined *only for <Cell data={this.props.percentages} /> in <Row /> even though that prop is not null there.
return {value: this.props.data};
},
handleChange: function(value) {
this.props.onChange(value);
this.setState({ value: value });
},
render: function() {
var edit = this.props.edit;
var showInput = edit ? 'group-class' : 'group-class hide';
var showText = edit ? 'hide' : 'btn btn-default btn-sm';
var val = this.props.isRates ? accounting.formatMoney(this.state.value) : this.state.value;
// {this.state.value} is undefined here for only the percentages object
// {this.props.data} is *not undefined*
var input = <ShippingInput type="text" label={this.props.label} value={this.state.value} ref="input"
isRates={this.props.isRates} groupClassName={showInput} labelClassName="label-class sr-only" onKeyDown={this.handleKeyDown} onChange={this.handleChange} />;
var text = (<a href="#" className={showText} onClick={this.handleClick}>{val}</a>);
return ( <td>{input}{text}</td> );
}
});
var Row = React.createClass({
propTypes: {
data: React.PropTypes.object.isRequired,
onCellChange: React.PropTypes.func.isRequired,
onRowCommit: React.PropTypes.func.isRequired
},
getInitialState: function() {
return {edit: false};
},
handleChange: function(prop, val) {
this.props.onCellChange(prop, val);
},
...
render: function() {
var edit = this.state.edit;
var text = edit ? 'fa fa-save fa-fw' : 'fa fa-edit fa-fw';
return <tr>
<Cell data={this.props.data.Canada} isRates={this.props.isRates} label="Canada" edit={edit} onRowEdit={this.handleRowEdit} onRowCommit={this.handleRowCommit} onChange={this.handleChange.bind(null, "Canada")} />
<Cell data={this.props.data.Us} isRates={this.props.isRates} label="United States" edit={edit} onRowEdit={this.handleRowEdit} onRowCommit={this.handleRowCommit} onChange={this.handleChange.bind(null, "Us")} />
<Cell data={this.props.data.International} isRates={this.props.isRates} label="International" edit={edit} onRowEdit={this.handleRowEdit} onRowCommit={this.handleRowCommit} onChange={this.handleChange.bind(null, "International")} />
<td>
<Button href="#" ref="commit" onClick={this.handleRowCommit} bsStyle="primary" bsSize="small"><span className={text}></span></Button>
</td>
</tr>;
}
});
var Grid = React.createClass({
propTypes: {
data: React.PropTypes.array.isRequired,
percentages: React.PropTypes.object.isRequired,
onCellChange: React.PropTypes.func.isRequired,
onRowCommit: React.PropTypes.func.isRequired
},
render: function() {
var rows = this.props.data.map(function(rowData, index) {
var id = rowData["Id"];
return <Row key={id} isRates={true} data={rowData} onCellChange={this.props.onCellChange.bind(null, index)} onRowCommit={this.props.onRowCommit.bind(null, index)} onRowDelete={this.props.onRowDelete.bind(null, index)} />;
}, this);
return (
<Table striped bordered hover responsive>
<thead>
<tr>
<th className="col-sm-4">Order Subtotal (up to)</th>
<th className="col-sm-2">Canada</th>
<th className="col-sm-2">US</th>
<th className="col-sm-2">International</th>
<th className="col-sm-1"></th>
</tr>
</thead>
<tbody>
{rows}
<tr><td colSpan="5">If the order subtotal is greater than the largest amount on the above chart, the following rates apply:</td></tr>
<Row key="Percentages" isRates={false} data={this.props.percentages} onCellChange={this.props.onPercentCellChange} onRowCommit={this.props.onPercentRowCommit} />
</tbody>
</Table>
);
}
});
var ShippingTable = React.createClass({
getInitialState: function() {
return this.props.initialData;
},
loadFromServer: function() {
$.getJSON(this.props.url, function(data) {
if (!data || data.Success === false) {
toastr.error('Error loading shipping costs. Please refresh the page and try again.');
} else if (this.isMounted()) {
// This change is not reflected in Row/Cell for this.state/props.percentages until after force a UI update (via handleAdd and handleCancel
// where this.state.add changes) even though a) percentages (a single object) holds valid values here and does all the way down to <Row />
// and b) there is no similar issue with this.state.data.
this.setState({ data: data.Value.ShippingCostMatrix, percentages: data.Value.ShippingPercentage });
}
}.bind(this));
},
componentDidMount: function() {
this.loadFromServer();
},
handleCellChange: function(rowIdx, prop, val) {
var row = copy(this.state.data[rowIdx]);
row[prop] = val;
var rows = this.state.data.slice();
rows[rowIdx] = row;
rows.sort(sortBySubtotal);
this.setState({data: rows});
},
handlePercentCellChange: function(prop, val) {
var row = copy(this.state.percentages);
row[prop] = val;
this.setState({percentages: row});
},
handleAdd: function(event) {
event.preventDefault();
this.setState({ add: true});
},
handleAddCancel: function(event) {
event.preventDefault();
this.setState({ add: false});
},
render: function() {
var ctrl;
if (this.state.add) {
ctrl = (<NewRow onAddCancel={this.handleAddCancel} onRowAdd={this.handleRowAdd} />);
}
else {
ctrl = (
<div>
<p><a onClick={this.handleAdd} className="btn btn-primary btn-lg">Add</a></p>
<Grid data={this.state.data} percentages={this.state.percentages}
onCellChange={this.handleCellChange} onPercentCellChange={this.handlePercentCellChange} onRowCommit={this.handleRowCommit} onPercentRowCommit={this.handlePercentRowCommit} onRowDelete={this.handleRowDelete} />
</div>
);
}
return <div>{ctrl}</div>;
}
});
//React.render(<ShippingTable initialData={ {data : [], percentages: { Canada: 1, Us: 1.25, International: 2.25, Id: 1 }, add: false} }
React.render(<ShippingTable initialData={ {data : [], percentages : {}, add: false} }
url="/admin/shipping/costs" update="/admin/shipping/update" create="/admin/shipping/create" delete="/admin/shipping/delete" updatePercentage="/admin/shipping/updatepercentage" />, document.getElementById('shipTable'));
回答by rallrall
getInitialState is supposed to return the initial component state, regardless of the props. If you really want to set a prop value in the state, you should use the componentWillMount hook. See docs.
无论道具如何,getInitialState 都应该返回初始组件状态。如果你真的想在状态中设置一个 prop 值,你应该使用 componentWillMount 钩子。请参阅文档。
Although it seems like you're working against the framework here. When you want to change a prop value, the parent component should react on this, and provide new props to the child component.
尽管您似乎在这里反对该框架。当你想改变一个 prop 值时,父组件应该对此做出反应,并为子组件提供新的 props。