Реакция setState обновляет неверный дочерний компонент - PullRequest
2 голосов
/ 16 июня 2019

Я создаю систему постов в блоге, где пользователи могут создавать несколько постов, отображаемых на одной странице, и при необходимости редактировать их с помощью редактора 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;   

1 Ответ

0 голосов
/ 16 июня 2019

OK. Я решил эту интересную проблему. Оказывается, Реакт не проблема. Используемая мной Materialise CSS Framework создала проблему, в частности M.AutoInit()

Вызов его не в том месте может вызвать проблемы с обработчиками событий из React.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...