javascript - React setState 更新不正确的子组件

标签 javascript reactjs typescript jsx

我正在构建一个博客帖子系统,用户可以在其中创建多个帖子,所有帖子都显示在一个页面上,并且可以在需要时直接在同一页面上使用 TinyMCE 编辑器对其进行编辑。

每篇博客文章都是它自己的 React 组件,具体称为 Post。帖子组件类负责呈现帖子(即标题、正文、作者等),这包括编辑帖子表单。它从 App 组件传递下来的 props 中获取 Post 数据,例如 title 和 body。

App 组件是入口点,它从我的服务器检索所有 JSON 格式的博客文章,为每个文章创建一个 Post 组件,传递相应的 props 并将整个 Post 组件推送到一个数组中。完成后,它会调用 this.setState() 并更新 posts 数组。

但是,如果创建了多个帖子并且我为单个 Post 组件调用了 this.handleEdit(),它将更新错误的 Post 组件的状态。

应用.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 类的状态,而不是它似乎更新了创建的帖子数组 App 中的另一个 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;   

最佳答案

好的。我已经解决了这个有趣的问题。事实证明 React 不是问题所在。我使用的 Materialise CSS Framework 造成了这个问题,特别是 M.AutoInit()

在错误的地方调用它会导致 React 的事件处理程序出现问题。

关于javascript - React setState 更新不正确的子组件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56616259/

相关文章:

node.js - 从 JSON 创建 TS 类实例时是否可以处理未知属性?

javascript - 如何从 .t​​pl 文件中动态出现的数字中删除逗号

javascript - Bootstrap : Why isnt my glyphicon showing?

ios - 仅在 React Native (iOS) 中的 <Text/> 组件的一侧添加边框

typescript - Typescript 2 中的尖括号和 "as' ' 有什么区别?

javascript - 模块解析失败 : Unexpected token

javascript - 从 QML ListView 委托(delegate)调用 JavaScript 对象方法

javascript - 如何将 javascript 变量值分配给文本框 - 在 PHP 中

php - 获取 img 的值

reactjs - 如何准备 React.js 组件以在 ClojureScript 中用作外部 Reagent 组件?