渲染的组件不响应其道具更新

时间:2019-12-11 00:56:53

标签: javascript reactjs react-native react-props react-state

我正在创建与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...

感谢您的帮助!

1 个答案:

答案 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。然后,您可以对其进行操作并再次返回一个对象以更新状态。对于切换事物或在您需要当前状态知道如何更新新状态时很有用。