React setState 更新不正确的子组件

Posted

技术标签:

【中文标题】React setState 更新不正确的子组件【英文标题】:React setState updates incorrect child component 【发布时间】:2019-10-30 03:48:49 【问题描述】:

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

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

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

但是,如果创建了多个帖子并且我为单个 Post 组件调用 this.handleEdit(),它将更新错误的 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 类的状态,而是似乎更新了另一个 Post帖子数组应用程序创建。特别是,“取消”按钮将调用this.disableEdit(),它将this.state.editEnabled 更新为false。但是,它不适用于当前的帖子,而是数组中的另一个帖子,看似随机...尝试打印与帖子关联的帖子标题也会给出不正确的帖子标题,如您在@987654327 中看到的那样@

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;   

【问题讨论】:

另见github.com/styled-components/styled-components/issues/1575 @JuniusL。该死的..这是否意味着我将不得不使用componentDidMount?我最初使用它,但在尝试在页面上呈现帖子时发现它非常慢。因此我切换到componentWillMount。不知道它已被弃用.. 嗯。将其更改为componentDidMount 似乎并不能解决此问题。我尝试刷新缓存,什么没有 是的,不会的,也许有些帖子的 id 相同? 他们不能有相同的 id,id 来自 mysql 数据库主键,具有自动增量。我还检查了 chrome react 调试器,它们是不同的。 【参考方案1】:

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

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

【讨论】:

以上是关于React setState 更新不正确的子组件的主要内容,如果未能解决你的问题,请参考以下文章

React 6/100 React原理 | setState | JSX语法转换 | 组件更新机制

setState源码分析

React - setState(...): 只能更新一个挂载或挂载的组件

React 警告:setState(...) 只能更新已安装或正在安装的组件

React之正确修改state

React生命周期, setState、props改变触发的钩子函数