语义 UI + 未定义数据的 React 问题
Semantic UI + React issue with undefined data
我对 React 很陌生。我在服务器上有 JSON 文件,通过 Node.JS 从中获取数据。我想将此数据的第一部分分配给 selectedProfile
,但它显示 undefined
。当我尝试分配它时,即使我尝试 this.state.selectedProfile.general.firstName
etc
,它也会在代码的 JSX 部分中显示 Cannot read property '*AnyProp*' of undefined
lower
import React from 'react';
import ReactDOM from 'react-dom';
import { Image, Search, Grid, Menu } from 'semantic-ui-react';
class ContentFeed extends React.Component {
constructor() {
super();
this.state = {
'items': [],
'prevName': '',
'i': 0,
'selectedProfile': []
}
this.getItems = this.getItems.bind(this);
}
componentWillMount() {
this.getItems();
}
getItems() {
var jsonData = [];
const xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status === 200) {
jsonData = JSON.parse(xhr.responseText);
this.setState({'items': jsonData});
this.setState({'prevName': jsonData[0].general.firstName + ' ' + jsonData[0].general.lastName});
document.getElementById(this.state.prevName).style = 'border: 3px solid black';
this.setState({'selectedProfile': this.state.items[0]});
console.log(jsonData);
} else {
console.log('Error: No 200 OK response');
}
}
xhr.open('get', 'http://localhost:8000/api/item');
xhr.send();
}
handleItemClick = (e, { name }) => {
if (name !== this.state.prevName) {
document.getElementById(name).style = 'border: 3px solid black';
document.getElementById(this.state.prevName).style = 'border: 1px solid black';
this.setState({'prevName': name});
let i = this.state.items.findIndex(element => {
return (element.general.firstName + ' ' + element.general.lastName) === name;
});
this.setState({'i': i});
this.setState({'selectedProfile': this.state.items[i]});
}
}
render() {
return (
<Grid>
<Grid.Column width={4} style={{'height': '700px', 'overflowY': 'scroll'}}>
<Search />
{this.state.items.map( (item, index) => {
return (
<Menu key={index} fluid vertical tabular>
<Menu.Item
key={item.general.firstName + ' ' + item.general.lastName}
name={item.general.firstName + ' ' + item.general.lastName}
id={item.general.firstName + ' ' + item.general.lastName}
onClick={this.handleItemClick}
style={{border: '1px solid black'}}
>
<Image src={item.general.avatar} style={{'width': '50px', 'height': '50px'}}/>
<p><br />{item.general.firstName + ' ' + item.general.lastName}</p>
<p>{item.job.title}</p>
</Menu.Item>
</Menu>
)
})}
</Grid.Column>
<Grid.Column stretched width={12}>
<div style={{'margin': 'auto'}} id="content">
<h2 style={{'marginTop': '10px', 'overflow': 'auto'}}>{this.state.selectedProfile[0]} {this.state.selectedProfile[1]}</h2>
<p></p>
<Image style={{'margin': '10px', 'float': 'left'}} src={this.state.selectedProfile[2]}/>
<p>Company: {this.state.selectedProfile[3]}</p>
<p>Title: {this.state.selectedProfile[4]}</p><br />
<p>Email: {this.state.selectedProfile[5]}</p>
<p>Phone: {this.state.selectedProfile[6]}</p>
<p>Address: {this.state.selectedProfile[7]}</p>
<p>City: {this.state.selectedProfile[8]}</p>
<p>ZIP: {this.state.selectedProfile[9]}</p>
<p>Country: {this.state.selectedProfile[10]}</p>
</div>
</Grid.Column>
</Grid>
);
}
}
你不能通过正常的赋值来扰乱状态。这行不通:
this.state.selectedProfile[0] = jsonData[0].general.firstName;
您应该循环遍历您的数据并使用 setState
相应地更改您的状态。
好吧,你这里似乎有多个问题。在您的 getItems()
xhr 回调中,您正在执行以下操作
jsonData = JSON.parse(xhr.responseText);
this.setState({'items': jsonData});
this.setState({'prevName': jsonData[0].general.firstName + ' ' + jsonData[0].general.lastName});
this.setState({'selectedProfile': this.state.items[0]});
但是 this.setState
是一个异步函数,这意味着在设置状态时,您不能假设 this.state
已经更新。为了争论,如果您在状态中设置多个值,请随意将它们组合在一个语句中,例如:
this.setState({
items: jsonData,
selectedProfile: jsonData[0],
prevName: jsonData[0].general.firstName + ' ' + jsonData[0].general.lastName
});
但是,这不是这里唯一的问题,而是您似乎遇到的数据流问题。您应该考虑到您在第一次渲染时还没有数据。因此,为了处理这个问题,您应该在渲染函数内部检查,在尝试渲染其任何属性之前是否确实存在 selectedProfile
。所以在那种情况下,您可以像这样更改渲染:
render() {
const { items, selectedProfile } = this.props;
if (!selectedProfile) {
// render nothing till the selected profile is there
return null;
}
// the rest of your rendering to return the grid (you can now use items & selectedProfile without the prefix this.state
}
这意味着无需将默认状态设置为空数组(现在不再是空数组,因为您现在正在为其分配一个对象,但我猜您在重构之前的评论时错过了这一点)
constructor() {
super();
this.state = {
items: [],
selectedProfile: null
};
}
代码中的一个重要说明是 DOM 操作,您不应该通过选择项目并改变它的传统方式进行 DOM 操作,事实上,强烈建议这样做将所有渲染留给 React。在那里,classNames
库可以帮助您进行有条件的 className
选择。
你当前的 this.handleItemClick
实现也会暂时失去 this
的上下文,你可以选择在你的构造函数中绑定 handleItemClick
(就像你似乎做的那样getItems
,其中不需要那个),或者使用事件处理程序的箭头函数。
所以,如果所有这些点都改变了,你有点想出一个类似的代码(注意我曾经跟踪 api,因为它易于使用并且有一个 public api 我可以写信给)
const Dog = ( { name, content, isSelected, onSelected } ) => {
return <div onClick={() => onSelected(name)} className={ classNames('breed', {'selected': isSelected}) }>{ name }</div>;
};
class DogList extends React.Component {
constructor() {
super();
this.state = {
loading: true,
selectedBreed: null
};
}
componentWillMount() {
this.fetchDogs();
}
componentDidUmount() {
this.mounted = false;
}
selectBreed( breedName ) {
this.setState( { selectedBreed: breedName } );
}
fetchDogs() {
fetch('https://dog.ceo/api/breeds/list/all')
.then( response => response.json() )
.then( json => {
this.setState( { breeds: json.message, loading: false } );
})
.catch( err => console.error( err ) );
}
render() {
const { loading, breeds, selectedBreed } = this.state;
if ( loading ) {
return <div>Loading dogs list</div>;
}
return <div className="dogs">{ Object.keys( breeds ).map( key => <Dog key={key} name={key} content={breeds[key]} isSelected={selectedBreed === key} onSelected={(...args) => this.selectBreed(...args)} /> ) }</div>;
}
}
const target = document.querySelector('#container');
ReactDOM.render( <DogList />, target );
.breed {
margin: 3px;
padding: 5px;
}
.selected {
border: solid #a0a0a0 1px;
background-color: #00ff00;
}
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
这对我有用。制作两个不同的渲染块。第一个 render
检查您的道具是否准备就绪。如果是这样,它会用 this.renderPage()
调用第二个。检查 Semantic React 以获取有关 Loader
.
的信息
render() {
return (this.props.ready) ? this.renderPage() : <Loader active>Getting data</Loader>;
}
renderPage() {
//your render code here
}
我对 React 很陌生。我在服务器上有 JSON 文件,通过 Node.JS 从中获取数据。我想将此数据的第一部分分配给 selectedProfile
,但它显示 undefined
。当我尝试分配它时,即使我尝试 this.state.selectedProfile.general.firstName
etc
Cannot read property '*AnyProp*' of undefined
lower
import React from 'react';
import ReactDOM from 'react-dom';
import { Image, Search, Grid, Menu } from 'semantic-ui-react';
class ContentFeed extends React.Component {
constructor() {
super();
this.state = {
'items': [],
'prevName': '',
'i': 0,
'selectedProfile': []
}
this.getItems = this.getItems.bind(this);
}
componentWillMount() {
this.getItems();
}
getItems() {
var jsonData = [];
const xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status === 200) {
jsonData = JSON.parse(xhr.responseText);
this.setState({'items': jsonData});
this.setState({'prevName': jsonData[0].general.firstName + ' ' + jsonData[0].general.lastName});
document.getElementById(this.state.prevName).style = 'border: 3px solid black';
this.setState({'selectedProfile': this.state.items[0]});
console.log(jsonData);
} else {
console.log('Error: No 200 OK response');
}
}
xhr.open('get', 'http://localhost:8000/api/item');
xhr.send();
}
handleItemClick = (e, { name }) => {
if (name !== this.state.prevName) {
document.getElementById(name).style = 'border: 3px solid black';
document.getElementById(this.state.prevName).style = 'border: 1px solid black';
this.setState({'prevName': name});
let i = this.state.items.findIndex(element => {
return (element.general.firstName + ' ' + element.general.lastName) === name;
});
this.setState({'i': i});
this.setState({'selectedProfile': this.state.items[i]});
}
}
render() {
return (
<Grid>
<Grid.Column width={4} style={{'height': '700px', 'overflowY': 'scroll'}}>
<Search />
{this.state.items.map( (item, index) => {
return (
<Menu key={index} fluid vertical tabular>
<Menu.Item
key={item.general.firstName + ' ' + item.general.lastName}
name={item.general.firstName + ' ' + item.general.lastName}
id={item.general.firstName + ' ' + item.general.lastName}
onClick={this.handleItemClick}
style={{border: '1px solid black'}}
>
<Image src={item.general.avatar} style={{'width': '50px', 'height': '50px'}}/>
<p><br />{item.general.firstName + ' ' + item.general.lastName}</p>
<p>{item.job.title}</p>
</Menu.Item>
</Menu>
)
})}
</Grid.Column>
<Grid.Column stretched width={12}>
<div style={{'margin': 'auto'}} id="content">
<h2 style={{'marginTop': '10px', 'overflow': 'auto'}}>{this.state.selectedProfile[0]} {this.state.selectedProfile[1]}</h2>
<p></p>
<Image style={{'margin': '10px', 'float': 'left'}} src={this.state.selectedProfile[2]}/>
<p>Company: {this.state.selectedProfile[3]}</p>
<p>Title: {this.state.selectedProfile[4]}</p><br />
<p>Email: {this.state.selectedProfile[5]}</p>
<p>Phone: {this.state.selectedProfile[6]}</p>
<p>Address: {this.state.selectedProfile[7]}</p>
<p>City: {this.state.selectedProfile[8]}</p>
<p>ZIP: {this.state.selectedProfile[9]}</p>
<p>Country: {this.state.selectedProfile[10]}</p>
</div>
</Grid.Column>
</Grid>
);
}
}
你不能通过正常的赋值来扰乱状态。这行不通:
this.state.selectedProfile[0] = jsonData[0].general.firstName;
您应该循环遍历您的数据并使用 setState
相应地更改您的状态。
好吧,你这里似乎有多个问题。在您的 getItems()
xhr 回调中,您正在执行以下操作
jsonData = JSON.parse(xhr.responseText);
this.setState({'items': jsonData});
this.setState({'prevName': jsonData[0].general.firstName + ' ' + jsonData[0].general.lastName});
this.setState({'selectedProfile': this.state.items[0]});
但是 this.setState
是一个异步函数,这意味着在设置状态时,您不能假设 this.state
已经更新。为了争论,如果您在状态中设置多个值,请随意将它们组合在一个语句中,例如:
this.setState({
items: jsonData,
selectedProfile: jsonData[0],
prevName: jsonData[0].general.firstName + ' ' + jsonData[0].general.lastName
});
但是,这不是这里唯一的问题,而是您似乎遇到的数据流问题。您应该考虑到您在第一次渲染时还没有数据。因此,为了处理这个问题,您应该在渲染函数内部检查,在尝试渲染其任何属性之前是否确实存在 selectedProfile
。所以在那种情况下,您可以像这样更改渲染:
render() {
const { items, selectedProfile } = this.props;
if (!selectedProfile) {
// render nothing till the selected profile is there
return null;
}
// the rest of your rendering to return the grid (you can now use items & selectedProfile without the prefix this.state
}
这意味着无需将默认状态设置为空数组(现在不再是空数组,因为您现在正在为其分配一个对象,但我猜您在重构之前的评论时错过了这一点)
constructor() {
super();
this.state = {
items: [],
selectedProfile: null
};
}
代码中的一个重要说明是 DOM 操作,您不应该通过选择项目并改变它的传统方式进行 DOM 操作,事实上,强烈建议这样做将所有渲染留给 React。在那里,classNames
库可以帮助您进行有条件的 className
选择。
你当前的 this.handleItemClick
实现也会暂时失去 this
的上下文,你可以选择在你的构造函数中绑定 handleItemClick
(就像你似乎做的那样getItems
,其中不需要那个),或者使用事件处理程序的箭头函数。
所以,如果所有这些点都改变了,你有点想出一个类似的代码(注意我曾经跟踪 api,因为它易于使用并且有一个 public api 我可以写信给)
const Dog = ( { name, content, isSelected, onSelected } ) => {
return <div onClick={() => onSelected(name)} className={ classNames('breed', {'selected': isSelected}) }>{ name }</div>;
};
class DogList extends React.Component {
constructor() {
super();
this.state = {
loading: true,
selectedBreed: null
};
}
componentWillMount() {
this.fetchDogs();
}
componentDidUmount() {
this.mounted = false;
}
selectBreed( breedName ) {
this.setState( { selectedBreed: breedName } );
}
fetchDogs() {
fetch('https://dog.ceo/api/breeds/list/all')
.then( response => response.json() )
.then( json => {
this.setState( { breeds: json.message, loading: false } );
})
.catch( err => console.error( err ) );
}
render() {
const { loading, breeds, selectedBreed } = this.state;
if ( loading ) {
return <div>Loading dogs list</div>;
}
return <div className="dogs">{ Object.keys( breeds ).map( key => <Dog key={key} name={key} content={breeds[key]} isSelected={selectedBreed === key} onSelected={(...args) => this.selectBreed(...args)} /> ) }</div>;
}
}
const target = document.querySelector('#container');
ReactDOM.render( <DogList />, target );
.breed {
margin: 3px;
padding: 5px;
}
.selected {
border: solid #a0a0a0 1px;
background-color: #00ff00;
}
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
这对我有用。制作两个不同的渲染块。第一个 render
检查您的道具是否准备就绪。如果是这样,它会用 this.renderPage()
调用第二个。检查 Semantic React 以获取有关 Loader
.
render() {
return (this.props.ready) ? this.renderPage() : <Loader active>Getting data</Loader>;
}
renderPage() {
//your render code here
}