React: Обновление в parent после setState в child

Рейтинг: 0Ответов: 1Опубликовано: 21.04.2023

Есть страница где я добавляю слова(+ перевод, заметку).

import React from 'react';
import {AddForm} from "./AddForm";
import {DataTable} from "./DataTable";
import {Word} from "./data/Word";
import {getTokenFromLocalStorage} from "../../../lib/common";
import {API_ROUTES, APP_ROUTES} from "../../../lib/config";
import {Navigate} from "react-router-dom";

interface State {
    words: Word[]
}

export class WordsPage extends React.Component<{authUser: string}, State>{

    constructor(props: any) {
        super(props);
        this.state = {words: []}

        this.handleAddWordEvent = this.handleAddWordEvent.bind(this);
        this.handleDeleteWordEvent = this.handleDeleteWordEvent.bind(this);
        this.handleEditWordEvent =  this.handleEditWordEvent.bind(this);
    }

    componentDidMount() {
        if(this.props.authUser) {
            this.updateWords()
        }
    }

    componentDidUpdate(prevProps: Readonly<{ authUser: string }>, prevState: Readonly<State>, snapshot?: any) {
        console.log("DidUpdate")
    }

    updateWords() {
        console.log("update words")
        const token = getTokenFromLocalStorage();
        fetch(API_ROUTES.GET_WORDS, {
            method: 'GET',
            headers: {
                Authorization: `Bearer ${token}`
            }
        }).then(response => response.text())
            .then(text => JSON.parse(text) as Word[])
            .then(data => {
                this.setState({words: data})
            })
    }

    handleAddWordEvent() {
        // console.log("Add word")
        this.updateWords()
    }

    handleDeleteWordEvent() {
        this.updateWords()
    }

    handleEditWordEvent() {
        this.updateWords()
    }

    render() {
        if(this.props.authUser) {
            return (
                <>
                    <h5>Сохраненные слова</h5>
                    <div className="divider"></div>
                    <div className="container">
                        <div className="row">
                            <AddForm onAddWordEvent={this.handleAddWordEvent}/>
                        </div>
                        <div className="row">
                            <DataTable words={this.state.words}
                                       onDeleteWord={this.handleDeleteWordEvent}
                                       onEditWord={this.handleDeleteWordEvent}
                            />
                        </div>
                    </div>
                </>
            )
        }
        return <Navigate to={APP_ROUTES.SIGN_IN} />
    }
}

Там есть таблица:

import React from 'react';
import {Word} from "./data/Word";
import {getTokenFromLocalStorage} from "../../../lib/common";
import {API_ROUTES} from "../../../lib/config";
import M from "materialize-css";
import {DataRow} from "./DataRow";
import {ConfirmDialog} from "../../modal/ConfirmDialog";

export class DataTable extends React.Component<
    { words: Word[], onDeleteWord: () => void, onEditWord: () => void},
    {selectWordId: number}> {

    constructor(props: any) {
        super(props);
    }

    componentDidMount() {
        const modalsElems = document.querySelectorAll('.modal');
        const modals = M.Modal.init(modalsElems, {});
    }

    render() {
        console.log("DataTable render()")
        console.log("words: ", this.props.words);
        return (
            <>
                <h5>Таблица</h5>
                <div>
                    <table >
                        <thead>
                        <tr>
                            <th>#</th>
                            <th>Оригинал</th>
                            <th>Перевод</th>
                            <th>Заметка</th>
                        </tr>
                        </thead>
                        <tbody>
                        {this?.props.words && this?.props.words.map((word) => {
                            return (
                                <DataRow key={word.id}
                                         word={word}
                                         selectWord={ () => this.setState({selectWordId: word.id}) }
                                         afterUpdateWord={this.props.onEditWord}
                                />
                            )
                        })
                        }
                        </tbody>
                    </table>
                    <ConfirmDialog onYes={this.confirmDelete}/>
                </div>
            </>
        )
    }
    confirmDelete = () => {
        this.deleteWord(this.state.selectWordId)
    }
    deleteWord = (id: number) => {
        const token = getTokenFromLocalStorage();
        fetch(API_ROUTES.DELETE_WORD, {
            method: 'POST',
            body: JSON.stringify({
                id: id
            }, null, 2),
            headers: {
                Authorization: `Bearer ${token}`,
                'Content-Type': 'application/json'
            }
        }).then(response => response.text())
            .then(data => {
                this.props.onDeleteWord()
            })
    }
}

Таблица содержит строки который описывает компонент:

import React from 'react';
import {Word} from "./data/Word";
import {getTokenFromLocalStorage} from "../../../lib/common";
import {API_ROUTES} from "../../../lib/config";

interface Props {
    word: Word
    selectWord: () => void
    afterUpdateWord: () => void
}

interface State {
    edit: boolean
    original: string
    translate: string
    note: string
}

export class DataRow extends React.Component<Props, State> {
    constructor(props: Props) {
        super(props);
        this.state = {edit: false, original: this.props.word.original, translate: this.props.word.translate, note: this.props.word.note};
    }

    editMode = <tr>
        <td>{this.props.word.id}</td>
        <td>
            <div className="input-field col s4">
                <textarea id="editOriginal"
                          className="materialize-textarea"
                          defaultValue={this.props.word.original} // value does not allow to write, so we use defaultValue
                          onChange={(e) => {this.setState({original: e.target.value})}}
                />
            </div>
        </td>
        <td>
            <div className="input-field col s4">
                <textarea id="editTranslate"
                          className="materialize-textarea"
                          defaultValue={this.props.word.translate} // value does not allow to write, so we use defaultValue
                          onChange={(e) => {this.setState({translate: e.target.value})}}
                />
            </div>
        </td>
        <td>
            <div className="input-field col s4">
                <textarea id="editNote"
                          className="materialize-textarea"
                          defaultValue={this.props.word.note} // value does not allow to write, so we use defaultValue
                          onChange={(e) => {this.setState({note: e.target.value})}}
                />
            </div>
        </td>
        <td>
            <button type="button"
                    className="btn-small"
                    onClick={() => this.updateWord()}
            >
                <i className="material-icons">done</i>
            </button>
        </td>
        <td>
            <button type="button"
                    className="btn-small gray"
                    onClick={() => this.setState({edit: false})}
            >
                <i className="material-icons">close</i>
            </button>
        </td>
    </tr>


    viewMode = <tr>
        <td>{this.props.word.id}</td>
        <td>{this.props.word.original}</td>
        <td>{this.props.word.translate}</td>
        <td>{this.props.word.note}</td>
        <td>
            <button type="button"
                    className="btn-small red modal-trigger"
                    data-target="confirmDeleteDialog"
                    onClick={this.props.selectWord}
            >
                <i className="material-icons">delete_forever</i>
            </button>
        </td>
        <td>
            <button type="button"
                    className="btn-small"
                    onClick={() => this.setState({edit: true})}
            >
                <i className="material-icons">edit</i>
            </button>
        </td>

    </tr>

    render() {

        console.log("DataRow render")
        let show;
        if(this.state.edit) {
            show = this.editMode
            console.log("DataRow editMode")
        } else {
            show = this.viewMode;
            console.log("DataRow viewMode")
        }

        console.log("DataRow word: ", this.props.word)

        return (
            <>
                {show}
            </>
        )
    }

    updateWord = () => {
        const token = getTokenFromLocalStorage();
        const {original, translate, note} = this.state;
        const id= this.props.word.id;

        fetch(API_ROUTES.EDIT_WORD, {
            method: "PUT",
            headers: {
                Authorization: `Bearer ${token}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({id, original, translate, note})
        }).then(response => {
            if(response.status === 200){
                // You can call some function after rendering
                this.setState({edit: false}, () => {
                    this.props.afterUpdateWord()
                })

            } else {
                alert("Что-то пошломалось: ");
            }
        })
    }
}

При редактировании строки, после нажатии кнопки "принять"(галочка) я делаю вызов в бэкенд и после успешного кода 200 выхожу из режима редактирования и вызываю функцию родителя(которая делает запрос в бэкенд для получения обновленного списка слов):

 this.setState({edit: false}, () => {
                    this.props.afterUpdateWord()
                })

Проблема - строка не меняется, остается со старым значением. Только после обновления страницы f5 видно что строка изменилась. Логами пробовал отследить этапы жизненого цикла, но вроде всё ровно. Также пробовал вызывать обновление списка слов спустя время(300 мс). Но не хочет она меняться.

Пример работы

Ответы

▲ 0Принят

В общем ребятки, спасибо за ментальную поддержку.

Я нашёл решение, каким то интуитивным образом, но как оно работает, логически не могу понять. Видимо сказывается отсутствие вменяемого опыта на react и js.

В компоненте DataRow я перенес переменную(jsx элемент) viewMode из области видимости компонента, в область видимости функции render. Вот так

render() {
        
        let show;
        if(this.state.edit) {
            show = this.editMode
        } else {
            show = <tr>
                <td>{this.props.word.id}</td>
                <td>{this.props.word.original}</td>
                <td>{this.props.word.translate}</td>
                <td>{this.props.word.note}</td>
                <td>
                    <button type="button"
                            className="btn-small red modal-trigger"
                            data-target="confirmDeleteDialog"
                            onClick={this.props.selectWord}
                    >
                        <i className="material-icons">delete_forever</i>
                    </button>
                </td>
                <td>
                    <button type="button"
                            className="btn-small"
                            onClick={() => this.setState({edit: true})}
                    >
                        <i className="material-icons">edit</i>
                    </button>
                </td>
            </tr>;
        }

        return (
            <>
                {show}
            </>
        )
    }

Видимо, что то не хочет менятся в jsx-элементе если он находиться в области видимости компонента, не смотря на то что пропсы поменялись. Но это лишь мои догадки