React: Обновление в parent после setState в child
Есть страница где я добавляю слова(+ перевод, заметку).
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 мс). Но не хочет она меняться.