简体   繁体   English

即使更改了 state,React Memo 也不会渲染组件

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

I used React.memo to memoize a component.我使用 React.memo 来记忆一个组件。 Since React.memo checks value by reference, I used a compare function that compares previous props and updated props and pass this function as a callback of React.memo.由于 React.memo 通过引用检查值,我使用了一个比较 function 来比较以前的道具和更新的道具并将这个 function 作为 React.memo 的回调传递。

The thing is, I used a custom hook that contains the data.问题是,我使用了一个包含数据的自定义挂钩。 This hook is called in the parent component and the component I want to memoize is the child component.这个钩子在父组件中调用,我要记忆的组件是子组件。 When the state changes, the first argument of compare function of React.memo is showing me the updated value.当 state 改变时,React.memo 的比较 function 的第一个参数显示更新后的值。 The state is getting changed in a different component but I am passing that state in to the child component then why it is showing me updated state (which is passed as a prop). state 在不同的组件中发生了变化,但我将 state 传递给子组件,然后为什么它显示更新的 state(作为道具传递)。

Here is the parent component: (Board)这是父组件:(板)

    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>
    )

This is the child component (CardHolder)这是子组件(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);

When the boardData state got changed, the prevProps parameter shows the updated state and that is where things go wrong.当 boardData state 发生变化时,prevProps 参数显示更新后的 state,这就是 go 错误的地方。

I tried to break the below code into a new component and return a Memoized version but that didnt work either.我试图将下面的代码分解成一个新组件并返回一个 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>}

to

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

Below is the component of WrapperHolder下面是 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>)
})

I found the solution but idk that is a valid solution or not.我找到了解决方案,但 idk 是否是一个有效的解决方案。 I made a count key of all arrays that are been passed in props.我为道具中传递的所有 arrays 制作了一个计数键。 I am also updating those counts as well when the array gets changed.当数组发生变化时,我也会更新这些计数。

The real question was why my compare function was sending me updated props in the first parameter as well?真正的问题是为什么我的比较 function 也在第一个参数中向我发送更新的道具? When I added the count key-value pair, it is working as expected but in the case of arrays, it is sending me an updated value and that is because of the deep copy and shallow copy.当我添加计数键值对时,它按预期工作,但在 arrays 的情况下,它向我发送更新值,这是因为深拷贝和浅拷贝。 Since arrays come in the shallow copy category, maybe that is why I am getting updated props in the first parameter but when counter was added (deep copy), the results are as expected!由于 arrays 属于浅拷贝类别,也许这就是为什么我在第一个参数中获取更新的道具,但是当添加计数器(深拷贝)时,结果符合预期!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM