При обновлении стейта вложенные чилдрены схлопываются
У меня есть список комментариев, полученный с сервера(массив объектов содержащих свойство parent:id родительского комментария). При помощи написанной в CommentsList функции nestComments я получаю комментарии со свойством children, в котором лежит массив вложенных в данный коммент комментариев, передаю их в компонент Comment в качестве пропса childrenComments и далее в компоненте Comment я отображаю их при помощи рекурсии. У каждого комментария есть опция лайка. По заданию не предусмотрено никаких запросов к серверу по изменению количества лайков каждого отдельного комментария, изменяется только общее количество лайков в шапке через экшен к стору + изменяем количество лайков локально у каждого комментария при помощи useState. Проблема следующая: при клике на лайк у комментария с чилдренами(вложенными комментариями) все вложенные комментарии схлопываются. Предполагаю что это происходит из-за ререндера и нужно вложенные комментарии мемоизировать, React.memo эффекта не дал, что-то не то делаю... Помогите пожалуйста решить эту проблему! Заранее благодарю. Ссылка на гит с кодом: https://github.com/Kohlenbaron28/kefir
//CommentsList.tsx
import {useState, useEffect, memo, useCallback, useMemo} from "react";
import {useSelector, useDispatch} from "react-redux";
import {formatDistanceToNow} from "date-fns";
import {HeartFilled, HeartOutlined} from "@ant-design/icons";
import {IState} from "../../types/IState";
import {ICommentProps} from "../../types/ICommentProps";
import * as actions from "../../store/actions";
import styles from "./Comment.module.scss";
const Comment = ({
avatar,
name,
text,
created,
likes,
childrenComments,
}: ICommentProps) => {
const dispatch = useDispatch();
const [liked, setLiked] = useState(false);
const authors = useSelector((state: IState) => state.authors);
childrenComments = useMemo(() => childrenComments, [childrenComments]);
const handleClick = async () => {
await setLiked((prev: any) => !prev);
if (!liked) {
dispatch(actions.incrementLikes());
} else dispatch(actions.decrementLikes());
};
const elems =
childrenComments !== undefined && childrenComments.length > 0 ? (
<ul>
{childrenComments.map((child, i, arr) => {
console.log(childrenComments, name);
arr.pop();
const pers = authors?.filter(
(el) => el.id === child.author,
)[0];
const MComment = memo(Comment);
return (
<MComment
avatar={pers?.avatar}
name={pers?.name}
created={child.created}
likes={child.likes}
text={child.text}
childrenComments={child.children}
key={child.id}
type="child"
/>
);
})}
</ul>
) : null;
console.log(elems);
return (
<li className={styles["comment"]}>
<section>
<div className={styles["comment__right"]}>
<div className={styles["comment__image"]}>
<img src={avatar} alt="avatar" />
</div>
<div className={styles["comment__text"]}>
<h2>{name}</h2>
<span className={styles["comment__time"]}>
{formatDistanceToNow(new Date(created))}
</span>
<p>{text}</p>
</div>
</div>
<figure onClick={handleClick}>
{liked ? <HeartFilled /> : <HeartOutlined />}
<figcaption>{liked ? ++likes : likes}</figcaption>
</figure>
</section>
{elems}
</li>
);
};
export default memo(Comment);
//Comment.tsx
import { useEffect } from "react";
import { connect, useSelector } from "react-redux";
import { bindActionCreators } from "redux";
import { IState } from "../../types/IState";
import { ICommentsListProps } from "../../types/ICommentsListProps";
import Comment from "../Comment/Comment";
import * as actions from "../../store/actions";
import styles from "./CommentsList.module.scss";
const CommentsList = ({ firstRender, comments }: ICommentsListProps) => {
useEffect(() => {
firstRender();
}, []);
const authors = useSelector((state: IState) => state.authors);
const nestComments = (commentList: any) => {
const commentMap: { [key: string]: any } = {};
commentList.forEach((comment: any) => (commentMap[comment.id] = comment));
commentList.forEach((comment: any) => {
if (comment.parent !== null) {
const parent = commentMap[comment.parent];
parent && (parent.children = parent.children || []).push(comment);
}
});
return commentList.filter((comment: any) => {
return comment.parent === null;
});
};
return (
<ul className={styles["commentsList"]}>
{nestComments(comments).map((comment: any) => {
const pers = authors.filter((el) => el.id === comment.author)[0];
return (
<Comment
key={comment.id}
avatar={pers?.avatar}
name={pers?.name}
created={comment.created}
likes={comment.likes}
text={comment.text}
childrenComments={comment.children}
/>
);
})}
</ul>
);
};
const mapStateToProps = (state: IState) => {
return {
comments: state.comments,
};
};
const mapDispatchToProps = (dispatch: any) => {
const { firstRender } = bindActionCreators(actions, dispatch);
return {
firstRender,
};
};
export default connect(mapStateToProps, mapDispatchToProps)(CommentsList);