簡體   English   中英

即使更改了 state,React Memo 也不會渲染組件

[英]React Memo not rendering the component even after state is changed

我使用 React.memo 來記憶一個組件。 由於 React.memo 通過引用檢查值,我使用了一個比較 function 來比較以前的道具和更新的道具並將這個 function 作為 React.memo 的回調傳遞。

問題是,我使用了一個包含數據的自定義掛鈎。 這個鈎子在父組件中調用,我要記憶的組件是子組件。 當 state 改變時,React.memo 的比較 function 的第一個參數顯示更新后的值。 state 在不同的組件中發生了變化,但我將 state 傳遞給子組件,然后為什么它顯示更新的 state(作為道具傳遞)。

這是父組件:(板)

    const data = useRef({ participant: { board: null, card: null }, board: null });

    // Refs for participants
    const participantsModal = useRef();

    // Refs for adding board
    const addBoardModal = useRef();
    const boardInput = useRef();

    // Refs for adding a new task
    const addCardModal = useRef();
    const piority = useRef();
    const taskName = useRef();

    // Refs for comments
    const commentModal = useRef();

    // ------------- This is the custom hook --------------- //
    const [boardData, setBoardData] = useBoardData(); 
    // ----------------------------------------------------- //
    const [newTaskParticipants, setNewTaskParticipants] = useState([{ name: "Hamza Nawab", checked: false }, { name: "Rahim Nawab", checked: false }, { name: "Khuzaima Nawab", checked: false }, { name: "Tony Stark", checked: false }, { name: "Bruce Wayne", checked: false }]);
    const [addRemoveParticipants, setAddRemoveParticipants] = useState([{ name: "Hamza Nawab", checked: false }, { name: "Rahim Nawab", checked: false }, { name: "Khuzaima Nawab", checked: false }, { name: "Tony Stark", checked: false }, { name: "Bruce Wayne", checked: false }]);
    const [participantsList, setParticipantsList] = useState([]);
    const [commentCard, setCommentCard] = useState({ card: 0, board: 0 });

    const removeBoardHandler = (index, e) => setBoardData(boardData.filter((_, i) => i !== index));

    // <========= Functions to trigger modals ==========> //
    const triggerCommentsModal = useCallback((e) => {
        const element = e.target.closest(".card");
        const card = Number(element.id.split("-")[1]);
        const board = Number(element.closest(".board").id.split("-")[1]);
        setCommentCard({ card: card, board: board });
        commentModal.current.triggerModal();
    }, [])

    const triggerParticipantsModal = useCallback((e) => {
        participantsModal.current.triggerModal();
        data.current.participant = { board: e.target.closest(".board").id.split("-")[1], card: e.target.closest(".card").id.split("-")[1] }
        const participants = boardData[data.current.participant.board].cards[data.current.participant.card].participants;
        setParticipantsList(participants);
        setAddRemoveParticipants(addRemoveParticipants.map(value => participants.includes(value.name) ? { ...value, checked: true } : { ...value, checked: false }));
        console.log(data.current)
    }, [addRemoveParticipants, boardData]);

    const triggerAddCardModal = useCallback((e) => {
        addCardModal.current.triggerModal();
        data.current.board = e.target.closest(".board");
    }, []);

    const triggerAddBoardModal = useCallback(() => addBoardModal.current.triggerModal(), []);

    // <========= Functions performing functionality on saving button =========> //

    // <========= For adding a new board =========> //
    const createNewBoard = () => {
        const obj = { id: Math.random().toString(36).slice(2), title: boardInput.current.value, cards: [] };
        setBoardData([...boardData, obj]);
        addBoardModal.current.triggerModal();
    };

    // <========= For adding a new task =========> //
    const createNewTask = () => {
        const boardIndex = Number(data.current.board.id.split("-")[1]);
        const participantsArray = newTaskParticipants.filter(value => value.checked).map(value => value.name);
        const newBoardData = boardData.map((value, i) => {
            if (i === boardIndex) {
                value.cards.push({ text: taskName.current.value, piority: piority.current === "Low Piority" ? 0 : piority.current === "High Piority" ? 2 : 1, participants: participantsArray })
            };
            return value;
        });
        setBoardData(newBoardData);
        setNewTaskParticipants(newTaskParticipants.map(value => {
            value.checked = false;
            return value;
        }));
        addCardModal.current.triggerModal();
    };

    // <========= For adding/removing participants from a task =========> //
    const manuplateParticipants = () => {
        participantsModal.current.triggerModal();
        const participants = addRemoveParticipants.filter(value => value.checked).map(value => value.name);
        setBoardData(boardData.map((value, i) => {
            if (i === Number(data.current.participant.board)) {
                value.cards[data.current.participant.card].participants = participants;
            }
            return value;
        }));
    };

    return (
        <AnimatePresence>
            <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.5 }} className="absolute bottom-0 right-0 flex grow flex-col items-end md:w-[90%] w-10/12 h-screen pt-20 px-6 bg-violet-100 overflow-auto">
                {/* Modal of comments */}
                <Modal ref={commentModal} heading="Comments" buttonText="Close" onSave={() => commentModal.current.triggerModal()}>
                    <div className='w-full flex flex-col items-center justify-center space-y-2 py-3'>
                        <div className='w-11/12 rounded-md space-y-1'>
                            <p><span className='font-bold'>Task:</span> This is a new world :D</p>
                            <p><span className='font-bold'>Details:</span> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
                        </div>
                        <hr className='bg-gray-300 w-[95%]' />
                        <div className='w-8/12'>
                            <div className='max-h-80 space-y-2 overflow-y-auto mb-4'>
                                {boardData && boardData[commentCard.board]?.cards[commentCard.card]?.chats?.map((chat, i) => <CommentCard key={i} name={chat.name} dateTime={chat.dateTime} text={chat.text} />)}
                            </div>
                            <div className='w-full flex items-center justify-between space-x-2'>
                                <UserCircle buttonClassName="cursor-default" />
                                <MessageInput placeholder="Enter your comment ..." type="text" />
                            </div>
                        </div>
                    </div>
                </Modal>
                {/* Modal of adding/remove participant for specific cards button */}
                <Modal ref={participantsModal} heading="Assign/Dismiss Participants" buttonText="Save changes" onSave={manuplateParticipants}>
                    <div className='flex justify-between grow w-full p-3 space-x-4'>
                        <div className='relative w-1/2 space-y-2'>
                            <h2 className='lg:text-lg md:text-base text-sm font-semibold'>Add/Remove Participants</h2>
                            <MultiSelectDropdown buttonText="Select Participants" state={addRemoveParticipants} setState={setAddRemoveParticipants} />
                            <div className='flex flex-wrap grow max-h-32 overflow-auto'>
                                <AnimatePresence>
                                    {addRemoveParticipants.map((value, i) => value.checked && <UsernameTag state={addRemoveParticipants} setState={setAddRemoveParticipants} key={i} name={value.name} />)}
                                </AnimatePresence>
                            </div>
                        </div>
                        <div className='w-px my-2 bg-slate-400'></div>
                        <div className='w-2/4 flex flex-col space-y-2'>
                            <h3 className='lg:text-lg md:text-base text-sm font-semibold'>Participants List</h3>
                            <div className='border border-slate-500 rounded-md flex flex-col items-center justify-start grow max-h-44 w-full overflow-y-auto'>
                                {participantsList.map((value, i) => <UserSmall key={i} name={value} />)}
                            </div>
                        </div>
                    </div>
                </Modal>
                {/* Modal of adding a new task to the board */}
                <Modal ref={addCardModal} heading="Add a new Task" buttonText="Save Changes" onSave={createNewTask}>
                    <div className='flex md:flex-row flex-col justify-between grow p-3 w-full md:space-x-4'>
                        <div className='flex md:w-1/2 w-full flex-col space-y-4'>
                            <Input labelClassName="p-0 md:p-1" text="Name" reference={taskName} />
                            <div className='w-full flex items-center md:pl-1 space-x-4 relative'>
                                <span>Piority</span>
                                <DropDown options={["Low Piority", "Medium Piority", "High Piority"]} buttonText="Select Piority" btnCSS="w-40" choosePiority={piority} />
                            </div>
                        </div>
                        <div className='w-px my-2 bg-slate-400 '></div>
                        <div className='relative md:w-1/2 space-y-2 flex flex-col'>
                            <div className='flex items-center space-x-4 w-full'>
                                <span className=''>Add Participants</span>
                                <div className='w-2/3'>
                                    <MultiSelectDropdown btnCSS="sm:w-3/4 md:w-full" buttonText="Select Participants" state={newTaskParticipants} setState={setNewTaskParticipants} />
                                </div>
                            </div>
                            <div className='flex flex-wrap max-h-20 overflow-auto'>
                                <AnimatePresence>
                                    {newTaskParticipants.map((value, i) => value.checked && <UsernameTag state={newTaskParticipants} setState={setNewTaskParticipants} key={i} name={value.name} />)}
                                </AnimatePresence>
                            </div>
                        </div>
                    </div>
                </Modal>
                {/* Modal for adding a new board */}
                <Modal ref={addBoardModal} heading="Add a new Board" buttonText="Save Changes" onSave={createNewBoard}>
                    <div className='flex justify-start items-center w-full grow px-4'>
                        <Input text="Board Name" className="w-[85%]" reference={boardInput} />
                    </div>
                </Modal>
                <div className="w-full py-5 mb-3 text-3xl text-gray-500 px-3">Studio Board</div>
                <div className='flex space-x-4 h-[85%] min-h-[28rem] overflow-x-auto w-full px-3'>
                    <AnimatePresence>
// --------------------------------- This is the child component I am talking about -------------------------------- //
                        {boardData?.length > 0 ? boardData.map((value, i) => <CardHolder comments={triggerCommentsModal} addTaskHandler={triggerAddCardModal} triggerModal={triggerParticipantsModal} key={value.title} id={value.id} index={i} title={value.title} card={value.cards} removeBoard={removeBoardHandler} changeData={setBoardData} data={boardData} />) : <h1>No boards</h1>}
                    </AnimatePresence>
                </div>
                <div className='fixed bottom-5 right-5 bg-gradient-to-br from-blue-400 to-indigo-300 w-10 h-10 md:w-14 md:h-14 rounded-full flex items-center justify-center hover:brightness-110 active:brightness-90 z-50' onClick={e => triggerAddBoardModal()}><FaPlus className='text-white scale-150 p-1 md:p-0' /></div>
            </motion.div>
        </AnimatePresence>
    )

這是子組件(CardHolder)

const CardHolder = (props) => {
    console.log("Card holder rendered!");

    const headingGradient = ["from-fuchsia-400 to-pink-600", "from-blue-300 to-indigo-500", "from-green-300 to-blue-300", "from-red-400 to-amber-400", "from-pink-400 to-blue-400", "from-orange-300 to-amber-600"];
    const randomIndex = useMemo(() => Math.floor(Math.random() * headingGradient.length), [headingGradient.length]);

    const dragItem = useRef();
    const dragOverItem = useRef();
    const cardTransferFlag = useRef();

    const btnClickHandler = (e) => props.triggerModal(e);

    const msgClickHandler = (e) => props.comments(e);

    return (
        <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0, y: 400 }} transition={{ duration: 1 }} id={`board-${props.index}`} className={cardHolderCSS()}>
            <div className={`w-full min-h-[0.25rem] rounded-t bg-gradient-to-r ${headingGradient[randomIndex]} `}></div>
            <div className="w-11/12 justify-self-center flex items-center justify-between">
                <h1 className="text-lg text-gray-500">{props.title}</h1>
                <button>
                    <FiX className='text-gray-500' onClick={(e) => props.removeBoard(props.index, e)} />
                </button>
            </div>
            <div className="card-drop !mt-0 h-0 flex opacity-0 p-0 items-center justify-around w-4/5 animate-bounce border border-gray-500 rounded-md shadow-md transition-all ease-in-out" draggable={true} onDrop={e => newBoardCardDrop(e, props.index, props.data, props.changeData)} onDragOver={e => newBoardCardDragOver(e, cardTransferFlag, dragItem, dragOverItem)} onDragLeave={e => newBoardCardDragLeave(e)}>
                <p className='text-gray-700 h-0 md:text-base text-xs'>Place Your Card Here</p>
                <FiCornerRightDown className='text-gray-700 md:scale-110 scale-90 h-0' />
            </div>
            <div className="space-y-3 w-full h-[80%] flex flex-col items-center overflow-y-scroll">
                <AnimatePresence>
                    {props.card?.map((value, i) => <Card key={i} onMsgClick={msgClickHandler} date={value.initiatedDate} participants={value.participants} onBtnClick={btnClickHandler} boardIndex={props.index} boardData={props.data} changeBoardData={props.changeData} index={i} text={value.text} piority={value.piority} dragItem={dragItem} dragOverItem={dragOverItem} cardTransferFlag={cardTransferFlag} />)}
                </AnimatePresence>
            </div>
            <div className="absolute bottom-0 w-full h-10 flex items-center justify-center bg-violet-100 z-30">
                <button className="p-3 flex items-center text-gray-400 hover:text-gray-500 justify-between space-x-2" onClick={props.addTaskHandler}><span>Add Task</span> <AiOutlinePlusCircle className='scale-125' /></button>
            </div>
        </motion.div>
    )
}

const compare = (prevProps, nextProps) => {
    console.log(prevProps);
    console.log(nextProps);
    return JSON.stringify(prevProps) === JSON.stringify(nextProps);
}

export default memo(CardHolder, compare);

當 boardData state 發生變化時,prevProps 參數顯示更新后的 state,這就是 go 錯誤的地方。

我試圖將下面的代碼分解成一個新組件並返回一個 Memoized 版本,但這也沒有用。

{boardData?.length > 0 ? boardData.map((value, i) => <CardHolder comments={triggerCommentsModal} addTaskHandler={triggerAddCardModal} triggerModal={triggerParticipantsModal} key={value.title} id={value.id} index={i} title={value.title} card={value.cards} removeBoard={removeBoardHandler} changeData={setBoardData} data={boardData} />) : <h1>No boards</h1>}

<WrapperHolder boardData={boardData} triggerCommentsModal={triggerCommentsModal} triggerAddCardModal={triggerAddCardModal} triggerParticipantsModal={triggerParticipantsModal} removeBoardHandler={removeBoardHandler} setBoardData={setBoardData} />

下面是 WrapperHolder 的組件

const WrapperHolder = React.memo((props) => {
    return (props.boardData?.length > 0 ? props.boardData.map((value, i) => <CardHolder comments={props.triggerCommentsModal} addTaskHandler={props.triggerAddCardModal} triggerModal={props.triggerParticipantsModal} key={value.title} id={value.id} index={i} title={value.title} card={value.cards} removeBoard={props.removeBoardHandler} changeData={props.setBoardData} data={props.boardData} />) : <h1>No boards</h1>)
})

我找到了解決方案,但 idk 是否是一個有效的解決方案。 我為道具中傳遞的所有 arrays 制作了一個計數鍵。 當數組發生變化時,我也會更新這些計數。

真正的問題是為什么我的比較 function 也在第一個參數中向我發送更新的道具? 當我添加計數鍵值對時,它按預期工作,但在 arrays 的情況下,它向我發送更新值,這是因為深拷貝和淺拷貝。 由於 arrays 屬於淺拷貝類別,也許這就是為什么我在第一個參數中獲取更新的道具,但是當添加計數器(深拷貝)時,結果符合預期!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM