Я создаю систему постов в блоге, где пользователи могут создавать несколько постов, отображаемых на одной странице, и при необходимости редактировать их с помощью редактора TinyMCE на той же странице.
Каждый отдельный пост в блоге - это собственный React Component, который называется Post. Класс Post Component отвечает за отображение Поста (т.е. заголовок, тело, автор и т. Д.), Включая форму редактирования поста. Он получает данные Post, такие как title и body, из реквизитов, которые были переданы компонентом App.
Компонент App является точкой входа, он получает все сообщения блога в формате JSON с моего сервера, создает компонент Post для каждого из них, передает соответствующие реквизиты и помещает весь компонент Post в массив. После этого он вызывает this.setState () и обновляет массив posts.
Однако, если создано несколько постов, и я вызываю this.handleEdit () для отдельного компонента Post, он обновит неправильное состояние компонента Post.
App.tsx
class App extends React.Component<IProps, IState> {
constructor(props: Readonly<IProps>) {
super(props);
this.state = {
posts: []
}
}
componentWillMount = () => {
var req = new HTTPRequest("GET", "/qa/posts")
req.execVoid(HTTP.RESPONSE.OK).then(function (data: []) {
var posts = [];
data.forEach(function (entry, index) {
posts.push(
<Post
id={entry['id']}
title={entry['title']}
body={entry['body']}
author={entry['author']}
date={entry['created_at']}
showDate={entry['showDate']}
deletePost={() => this.deleteComponent(index)}
key={index}
/>
)
}.bind(this))
this.updatePosts(posts)
}.bind(this))
}
updatePosts = (posts: Array<any>) => {
if (posts.length == 0) {
posts.push(
<div className="card" key={1}>
<div className="card-content">
No posts to show :)
</div>
</div>
)
}
this.setState({
posts: posts
})
}
deleteComponent = (key: number) => {
let posts = this.state.posts.filter(function (value, index) {
return index != key;
})
this.updatePosts(posts);
}
componentDidMount = () => {
}
render(): React.ReactNode {
return (
<div>
{this.state.posts}
</div>
)
}
}
export default App;
Когда я нажимаю кнопку «Отмена», показанную в методе this.actions()
, с this.state.editEnabled
, установленным в true, он не будет обновлять состояние текущего класса Post, вместо этого он, похоже, обновляет другое сообщение в Создан массив сообщений. В частности, кнопка «Отмена» вызовет this.disableEdit()
, который обновит this.state.editEnabled
до false. Однако он не делает это для текущей публикации, но другая запись в массиве, по-видимому, наугад ... попытка распечатать заголовок сообщения, связанный с сообщением, также даст неправильное название сообщения, как вы можете видеть в this.disableEdit()
Post.tsx
class Post extends React.Component<IProps, IState> {
constructor(props: Readonly<IProps>) {
super(props);
this.state = {
id: -1,
title: "",
body: "",
author: "",
date: "",
showDate: true,
editEnabled: false,
showProgressBar: false,
edit: {
title: "",
}
};
}
componentDidMount = () => {
this.setState({
id: this.props['id'],
title: this.props['title'],
body: this.props['body'],
author: "",//this.props['author'],
date: this.convertToReadableDate(this.props['date']),
showDate: !!this.props['showDate'],
})
tinymce.init({
selector: "#edit_body_" + this.props['id'],
skin_url: '/lib/tinymce/skins/ui/oxide',
})
}
convertToReadableDate(unix_timestamp: number): string {
var date = new Date(unix_timestamp * 1000);
return date.toISOString().split("T")[0];
}
handleDelete = () => {
if (confirm("Are you sure you would like to delete this post?")) {
var req = new HTTPRequest("DELETE", "/qa/posts/" + this.state.id);
req.execVoid(HTTP.RESPONSE.OK)
.then(function () {
this.props.deletePost();
M.toast({ html: "Your post was deleted!", classes: "green" })
}.bind(this))
.catch(function (err: Error) {
M.toast({
html: "We have trouble deleting your post. Try again later",
classes: "red"
});
console.error(err.message);
}.bind(this))
}
}
promptSaveChange = () => {
if (this.state.title != this.state.edit.title || tinymce.get('edit_body_' + this.props.id).getContent() !== this.state.body) {
return confirm("You have unsaved changes. Are you sure you would like to proceed?")
} else {
return true;
}
}
handleEdit = () => {
if (this.state.editEnabled) {
if (this.promptSaveChange()) {
this.disableEdit();
}
} else {
this.enableEdit();
tinymce.get('edit_body_' + this.props.id).setContent(this.state.body);
}
}
resetChanges = () => {
this.setState({
edit: {
title: this.state.title
}
})
tinymce.get('edit_body_' + this.props.id).setContent(this.state.body);
}
handleEditSave = () => {
this.showProgress();
var req = new HTTPRequest("PATCH", "/qa/posts/" + this.state.id);
var body_content = tinymce.get('edit_body_' + this.props.id).getContent();
req.execAsJSON({
title: this.state.edit.title,
body: body_content
}, HTTP.RESPONSE.ACCEPTED).then(function (ret) {
this.setState({
title: this.state.edit.title,
body: body_content
});
this.disableEdit();
M.toast({
html: ret['msg'],
classes: 'green'
})
}.bind(this)).catch(function (err: Error) {
console.log(err.message);
M.toast({
html: "We had trouble updating the post. Try again later."
})
}.bind(this)).finally(function () {
this.hideProgress();
})
}
handleTitleEdit = (e) => {
this.setState({
edit: {
title: e.target.value
}
})
}
enableEdit = () => {
this.setState({
editEnabled: true,
edit: {
title: this.state.title
}
}, function () {
M.AutoInit();
})
}
disableEdit = () => {
console.log('disabled: ' + this.state.title);
this.setState({
editEnabled: false
})
}
showProgress = () => {
this.setState({
showProgressBar: true
})
}
hideProgress = () => {
this.setState({
showProgressBar: false
})
}
content = () => {
return (
<div>
<div style={{ display: this.state.editEnabled ? 'none' : null }}>
<span className="card-title">{this.state.title}</span>
<div dangerouslySetInnerHTML={{ __html: this.state.body }}></div>
<small> {this.state.showDate ? "Posted at: " + this.state.date : ""}</small>
</div>
<div style={{ display: this.state.editEnabled ? null : 'none' }}>
<input type="text" name="title" value={this.state.edit.title} placeholder={this.state.title} onChange={this.handleTitleEdit} />
<textarea id={"edit_body_" + this.props.id}></textarea>
</div>
</div>
)
}
actions = () => {
return (
<>
<div className="row" style={{ display: this.state.editEnabled ? null : 'none' }}>
<a className="btn-small green waves-effect" onClick={this.handleEditSave}><i className="material-icons left">save</i> Save</a>
<a className='dropdown-trigger btn-flat blue-text' href='#' data-target='edit-options'>More</a>
<ul id='edit-options' className='dropdown-content'>
<li>
<a href="#!" className="orange-text" onClick={this.resetChanges}>Reset Changes</a>
</li>
<li>
<a href="#!" className="orange-text" onClick={this.handleEdit}>Cancel</a>
</li>
<li>
<a href="#!" className="red-text" onClick={this.handleDelete}>Delete</a>
</li>
</ul>
<div className="progress" style={{ display: this.state.showProgressBar ? null : "none" }}>
<div className="indeterminate"></div>
</div>
</div>
<div className="row" style={{ display: this.state.editEnabled ? 'none' : null }}>
<a className="btn-small orange waves-effect" onClick={this.handleEdit}><i className="material-icons left">edit</i> Edit</a>
</div>
</>
)
}
render(): React.ReactNode {
return (
<div className="card">
<div className="card-content">
{this.content()}
</div>
<div className="card-action">
{this.actions()}
</div>
</div>
)
}
}
export default Post;