javascript - 渲染的组件不响应其道具更新
问题描述
我正在创建与 Trello“相似”的 React App。在我们有项目,项目有板,板有栏,栏有任务等等。默认情况下,只有包含带有板名称的栏的项目应该是可见的 - 其余的应该是隐藏的。如果您单击带有板标题的栏 - 该板的全部内容应该是可见的。此外,当您再次单击此栏时,它应该再次隐藏。我希望这很清楚。
问题是,我有在一个组件(ProjectView)中呈现板名称的栏,其中包含许多子组件(Board),这些组件最终可以呈现每个板的内容(BoardView)。在 ProjectView 中,我有一张地图 (visibilityMap),它将 isHidden-boolean 分配给董事会的名称。这张map中对应的值被传递给每个Board组件的props,Board组件将它传递给BoardView,在那里它被用来为一个有内容的div定义隐藏的属性值。此外,在 ProjectView 中,我有一个函数可以在单击适当的板名后更改地图中适当的布尔值。一切都正确渲染,我可以在日志中看到,在单击 board-name 后,visibilityMap 中的特定值发生了变化,但它没有
我在这上面花了很多时间并尝试了很多奇怪的事情,但我不知道出了什么问题。下面我附上了所描述组件的代码和两个屏幕截图 - 在单击板名称之前和之后。
ProjectView - 几乎所有东西都在renderBoard上
import React from 'react';
import {Button} from "react-bootstrap";
import TextField from '@material-ui/core/TextField';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import Board from "../controllers/Board";
import {PhotoshopPicker} from 'react-color';
require("../../styles/Project.css");
require("../../styles/Board.css");
class ProjectView extends React.Component {
constructor(props) {
super(props);
this.state = {
render: false,
modalShow: false,
modalAddShow: false,
modalBoard: "",
boardName: "",
boardBackground: "orange",
newName: "",
newBackground: "",
showAddBackground: false,
showChangeBackground: false,
pickedBackground: "orange",
validate: true,
visibilityMap: new Map()
};
this.handleChange = this.handleChange.bind(this);
}
renderBoards = () => {
if (this.props.boards.length === 0) {
return (<div className="no-content">Brak board'ów!</div>)
}
if (this.state.visibilityMap.size === 0) {
this.props.boards.map(board => this.state.visibilityMap.set(board.name, true))
}
return this.props.boards.map(board =>
this.renderBoard(board)
);
};
renderModal = (board) => {
this.setState({
modalShow: true,
modalBoard: board,
newName: board.name,
newBackground: board.background,
})
};
handleChangeColor = (color) => {
this.setState({
pickedBackground: color
})
};
handleCancelColor = () => {
this.setState({
showAddBackground: false,
showChangeBackground: false,
})
};
handleAcceptAddColor = () => {
const newColor = this.state.pickedBackground.hex;
this.setState({
boardBackground: newColor,
showAddBackground: false
})
};
handleAcceptChangeColor = () => {
const newColor = this.state.pickedBackground.hex;
this.setState({
newBackground: newColor,
showChangeBackground: false
})
};
renderBoard = (board) => {
const handleClose = () => {
this.setState({
newName: "",
newBackground: "",
modalShow: false
})
};
const switchVisibility = () => {
const oldValue = this.state.visibilityMap.get(board.name)
this.state.visibilityMap.set(board.name, !oldValue)
}
const handleEdit = () => {
this.props.handleEdit(this.state.modalBoard.name, this.state.newName, this.state.newBackground);
handleClose();
this.setState({
modalBoard: ""
});
};
return (
<div className="board" style={{backgroundColor: board.background}}>
<div className="bookmark board-head" onClick={switchVisibility}>
{board.name}
<Button
className="action-button delete"
id={board.name}
onClick={this.handleDelete}
variant="danger"
>
X
</Button>
<Button
className="action-button edit"
id={board.name}
onClick={() => this.renderModal(board)}
variant="warning"
>
O
</Button>
<Dialog open={this.state.modalShow} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Edytuj boarda {this.state.modalBoard.name}</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
name="newName"
label="Nazwa boarda"
type="text"
onChange={this.handleChange}
value={this.state.newName}
fullWidth
/>
<div style={{display: "flex", flexFlow: "nowrap row"}}>
<Button
variant="light"
onClick={() => this.setState({
showChangeBackground: true,
pickedBackground: this.state.newBackground
})}
>
Kolor:
</Button>
<div className="color-box"
style={{background: this.state.newBackground}}>
</div>
<Dialog open={this.state.showChangeBackground} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Kolor boarda {board.name}</DialogTitle>
<DialogContent>
<PhotoshopPicker
header="Wybierz kolor"
onAccept={this.handleAcceptChangeColor}
onCancel={this.handleCancelColor}
color={this.state.pickedBackground}
onChangeComplete={this.handleChangeColor}/>
</DialogContent>
</Dialog>
</div>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Anuluj
</Button>
<Button
onClick={handleEdit}
disabled={this.state.newName === "" || !this.state.validate}
color="primary">
Zapisz
</Button>
</DialogActions>
</Dialog>
</div>
<Board isHidden={this.state.visibilityMap.get(board.name)} boardReference={board.ref}
name={board.name}/>
</div>
)
};
renderAddModal = () => {
this.setState({
modalAddShow: true,
})
};
renderAddBoard = () => {
const handleClose = () => {
this.setState({
modalAddShow: false,
newName: ""
})
};
const handleAdd = () => {
this.props.handleSubmit(this.state.boardName, this.state.boardBackground);
this.setState({boardName: "", boardBackground: "orange"});
handleClose();
this.setState({
modalBoard: ""
});
};
return (
<div>
<Button
className="add-button new-board"
onClick={this.renderAddModal}
variant="success"
>
+
</Button>
<Dialog open={this.state.modalAddShow} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Dodaj board'a</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
name="boardName"
label="Nazwa"
type="text"
onChange={this.handleChange}
value={this.state.boardName}
fullWidth
>
</TextField>
<div style={{display: "flex", flexFlow: "nowrap row"}}>
<Button
margin="dense"
fullWidth
variant="light"
onClick={() => this.setState({
showAddBackground: true
})}
>
Kolor:
</Button>
<div className="color-box"
style={{background: this.state.boardBackground}}>
</div>
</div>
<Dialog open={this.state.showAddBackground} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Kolor nowego boarda</DialogTitle>
<DialogContent>
<PhotoshopPicker
header="Wybierz kolor"
onAccept={this.handleAcceptAddColor}
onCancel={this.handleCancelColor}
color={this.state.pickedBackground}
onChangeComplete={this.handleChangeColor}/>
</DialogContent>
</Dialog>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Anuluj
</Button>
<Button onClick={handleAdd}
disabled={this.state.boardName === "" || !this.state.validate}
color="primary">
Dodaj
</Button>
</DialogActions>
</Dialog>
</div>
);
};
handleDelete = e => {
e.preventDefault();
this.props.handleDelete(e.target.id);
};
handleChange = e => {
e.preventDefault();
const isValid = this.validator(e.target)
this.setState({
[e.target.name]: e.target.value,
validate: isValid
});
};
validator = input => {
if (input.name === "newName" && this.state.modalBoard.name === input.value)
return true
const filter = this.props.boards.find(board =>
board.name === input.value
)
return typeof (filter) === "undefined"
}
componentDidMount() {
setTimeout(function () {
this.setState({render: true})
}.bind(this), 1000)
}
render() {
let renderContainer = false;
if (this.state.render) {
renderContainer =
<div className="project-body">
{this.renderBoards()}
{this.renderAddBoard()}
</div>
}
return (
renderContainer
)
}
}
export default ProjectView;
Board - 仅从道具中获取 isHidden 并将其传递给 BoardView 道具
import React from 'react';
import BoardView from '../views/BoardView';
import {ColumnService} from '../../services/ColumnService';
class Board extends React.Component {
constructor(props) {
super(props)
this.boardReference = this.props.boardReference;
this.columnService = new ColumnService();
this.state = {
columns: [],
isHidden: this.props.isHidden
}
}
componentDidMount() {
this.setDatabaseListener();
}
handleSubmit = (columnName, order) => {
this.columnService.addColumn(columnName, order, this.boardReference);
}
handleEdit = (name, newColumnName, newColumnOrder) => {
this.columnService.editColumn(name, newColumnName, newColumnOrder, this.boardReference);
}
handleDelete = data => {
const name = data.id;
this.columnService.deleteColumn(name, this.boardReference);
}
render() {
return (
<BoardView
isHidden={this.state.isHidden}
columns={this.state.columns}
handleSubmit={this.handleSubmit}
handleEdit={this.handleEdit}
handleDelete={this.handleDelete}
/>
)
}
setDatabaseListener() {
this.columnService.columnRef(this.boardReference).onSnapshot(data => {
const listOfFetchedColumns = [];
data.docs.forEach(doc => {
const columnReference = doc.ref;
const data = doc.data();
data['ref'] = columnReference;
listOfFetchedColumns.push(data);
console.log('fetched columns', data);
});
listOfFetchedColumns.sort((a, b) => (a.order > b.order) ? 1 : -1)
this.setState({
columns: listOfFetchedColumns
});
});
}
}
export default Board;
BoardView - 最后最重要的,在渲染
import React from 'react';
import {Button} from "react-bootstrap";
import TextField from '@material-ui/core/TextField';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import Column from "../controllers/Column";
require("../../styles/Board.css");
require("../../styles/Column.css");
class BoardView extends React.Component {
constructor(props) {
super(props);
this.state = {
render: false,
modalShow: false,
modalAddShow: false,
modalColumn: "",
columnName: "",
columnOrder: "",
newName: "",
newOrder: "",
validate: true,
hideColumns: true,
isHidden: this.props.isHidden
};
this.handleChange = this.handleChange.bind(this);
}
renderColumns = () => {
if (this.props.columns.length === 0) {
return (<div className="no-content">Brak kolumn!</div>)
}
return this.props.columns.map(column =>
this.renderColumn(column)
);
};
renderModal = (column) => {
this.setState({
modalShow: true,
modalColumn: column,
newName: column.name,
newOrder: column.order
})
};
renderColumn = (column) => {
const handleClose = () => {
this.setState({
modalShow: false,
newName: "",
newOrder: ""
})
};
const handleEdit = () => {
handleClose();
this.props.handleEdit(this.state.modalColumn.name, this.state.newName, parseInt(this.state.newOrder));
this.setState({
modalColumn: ""
});
};
return (
<div className="column">
<div className="bookmark column-head">
{column.name}
<Button
className="action-button delete"
id={column.name}
onClick={this.handleDelete}
variant="danger"
>
X
</Button>
<Button
className="action-button edit"
id={column.name}
onClick={() => this.renderModal(column)}
variant="warning"
>
O
</Button>
<Dialog open={this.state.modalShow} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Edytuj kolumnę {this.state.modalColumn.name}</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
name="newName"
label="Nazwa kolumny"
type="text"
onChange={this.handleChange}
value={this.state.newName}
fullWidth
/>
<TextField
autoFocus
margin="dense"
name="newOrder"
label="Kolejność kolumny"
type="number"
min={1}
onChange={this.handleChange}
value={this.state.newOrder}
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Anuluj
</Button>
<Button onClick={handleEdit}
disabled={
this.state.newName === "" ||
this.state.newOrder === "" ||
this.state.newOrder < 0 ||
!this.state.validate
}
color="primary">
Zapisz
</Button>
</DialogActions>
</Dialog>
</div>
<Column columnReference={column.ref} name={column.name}/>
</div>
)
};
renderAddModal = () => {
this.setState({
modalAddShow: true,
})
};
renderAddColumn = () => {
const handleClose = () => {
this.setState({
modalAddShow: false,
newName: ""
})
};
const handleAdd = () => {
this.props.handleSubmit(this.state.columnName, parseInt(this.state.columnOrder));
this.setState({columnName: "", columnOrder: ""});
handleClose();
this.setState({
modalColumn: ""
});
};
return (
<div className="new-column-button-wrapper">
<Button
className="add-button new-column"
onClick={this.renderAddModal}
variant="success"
>
+
</Button>
<Dialog open={this.state.modalAddShow} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Dodaj kolumnę</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
name="columnName"
label="Nazwa kolumny"
type="text"
onChange={this.handleChange}
value={this.state.columnName}
fullWidth
>
</TextField>
<TextField
margin="dense"
name="columnOrder"
label="Kolejność"
type="number"
min={1}
onChange={this.handleChange}
value={this.state.columnOrder}
fullWidth
>
</TextField>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Anuluj
</Button>
<Button onClick={handleAdd} disabled={
this.state.columnName === "" ||
this.state.columnOrder === "" ||
this.state.columnOrder < 0 ||
!this.state.validate
} color="primary">
Dodaj
</Button>
</DialogActions>
</Dialog>
</div>
);
};
handleDelete = e => {
e.preventDefault();
this.props.handleDelete(e.target);
};
handleChange = e => {
e.preventDefault();
const isValid = this.validator(e.target)
this.setState({
[e.target.name]: e.target.value,
validate: isValid
});
};
validator = input => {
if (input.name === "newName" && this.state.modalColumn.name === input.value)
return true
const filter = this.props.columns.find(column =>
column.name === input.value
)
return typeof (filter) === "undefined"
}
componentDidMount() {
setTimeout(function () {
this.setState({render: true})
}.bind(this), 1000)
}
render() {
let renderContainer = false;
if (this.state.render) {
renderContainer =
<div className="board-body" hidden={this.state.isHidden}>
{this.renderColumns()}
{this.renderAddColumn()}
</div>
}
return (
renderContainer
)
}
}
export default BoardView;
感谢您的帮助!
解决方案
我的猜测是,因为您直接使用地图设置器(在您的 switchVisibility 方法中)更改了 visibilityMap 上的属性,而不是使用 setState 设置它,所以它不会重新渲染。React 使用 setState 知道何时检查状态变化,这会导致重新渲染。
所以 prop 正在改变,因为你确实改变了它,但 React 并没有重新渲染,因为它从来没有注意到 state 曾经改变过。
我在使用 javaScript 时从未使用过 Map,但我假设您选择它而不是对象,因为您想按顺序索引它?
如果是这样,我猜你需要在你的 switchVisibility 方法中做这样的事情:
this.setState(state => {
let oldValue = state.visibilityMap.get(board.name)
state.visibilityMap.set(board.name, !oldValue)
return state
)};
这将更新 state 对象以包含新的 visibilityMap 并更新了 prop,然后 React 应该重新渲染。
如果这对您来说看起来很神奇,那么 setState 方法,当您将回调传递给它时,它会自动将当前状态作为参数传递给该 callBack。然后,您可以对其进行操作并再次返回一个对象以更新状态。用于切换事物,或者当您需要当前状态来知道如何更新新状态时。
推荐阅读
- python - 如何在 exe 文件上实现自动更新
- python - 为什么执行插入查询时出现错误
- java - 正则表达式仅匹配第一次出现
- mongodb - 如何将 dataType 文本更改为 ObjectId 作为 Mapreduce 作业中的输入键
- javascript - 如何将 CKEditor 上的主题/皮肤从浅色更改为深色?
- bash - 如何根据文件名 tar 文件?
- git - 即使在冲突解决后如何查看三向 git diff
- javascript - 检测运行了哪个 npm 脚本命令
- c# - 使用依赖注入的 Mapster 全局配置
- javascript - 如何在打开警报框时关闭 jquery 对话框