我正在创建与Trello类似的React App。在我们有项目的情况下,项目有电路板,电路板有列,列有任务等。默认情况下,只有包含其木板名称的条的Project应该可见-其余部分应该隐藏。而且,如果您单击带有板标题的栏,则该板的全部内容应该可见。另外,再次单击该栏时,它应该再次隐藏。我希望这很清楚。
问题是,我在一个组件(ProjectView)中绘制了带有板名的栏,其中包含许多子组件(Board),这些组件最终可以呈现每个板(BoardView)的内容。在ProjectView中,我有一张地图(visibilityMap),该地图将isHidden-boolean分配给电路板的名称。该映射中的对应值传递到每个Board组件的prop,而Board组件将其传递到BoardView,在其中它用于为包含内容的div定义隐藏属性值。另外,在ProjectView中,我有一个函数,可在单击适当的板子名称后更改地图中的适当布尔值。一切正常呈现后,我可以在日志中看到,单击木板名称后,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;
董事会-仅从道具中获取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 -最重要的是在 render
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;
Board content is visible - click on board-name to hide it...
Board content is hidden - click on board-name to show it...
感谢您的帮助!
答案 0 :(得分:0)
我的猜测是,因为您是直接使用maps setter(在switchVisibility方法中)而不是使用setState更改可见性Map上的属性,所以不会重新渲染。 React使用setState知道何时检查状态变化,这会导致重新渲染。
所以道具正在更改,因为您确实进行了更改,但是React并未重新渲染,因为它从未注意到状态曾经更改过。
在使用JavaScript时,我从未使用过Map,但我假设您选择了Map而不是对象,因为您想按顺序对其进行索引?
如果是这样,我想您需要在switchVisibility方法中执行以下操作:
this.setState(state => {
let oldValue = state.visibilityMap.get(board.name)
state.visibilityMap.set(board.name, !oldValue)
return state
)};
这将更新状态对象以包含具有更新的prop的新的visibilityMap,然后应重新渲染React。
如果这对您来说像魔术,则setState方法在传递给您时,回调会自动将当前状态作为参数传递给该callBack。然后,您可以对其进行操作并再次返回一个对象以更新状态。对于切换事物或在您需要当前状态知道如何更新新状态时很有用。