Batch-resolve channels on doCommentList fetch

This commit is contained in:
Rafael 2022-03-02 09:10:52 -03:00 committed by Thomas Zarebczan
parent 7c304702d6
commit 96e7fda26a
7 changed files with 79 additions and 164 deletions

View file

@ -148,7 +148,7 @@ declare type CommentListParams = {
channel_name?: string, // signing channel name of claim (enables 'commentsEnabled' check) channel_name?: string, // signing channel name of claim (enables 'commentsEnabled' check)
channel_id?: string, // signing channel claim id of claim (enables 'commentsEnabled' check) channel_id?: string, // signing channel claim id of claim (enables 'commentsEnabled' check)
author_claim_id?: string, // filters comments to just this author author_claim_id?: string, // filters comments to just this author
parent_id?: string, // filters comments to those under this thread parent_id?: ?string, // filters comments to those under this thread
top_level?: boolean, // filters to only top level comments top_level?: boolean, // filters to only top level comments
hidden?: boolean, // if true, will show hidden comments as well hidden?: boolean, // if true, will show hidden comments as well
sort_by?: number, // @see: ui/constants/comments.js::SORT_BY sort_by?: number, // @see: ui/constants/comments.js::SORT_BY

View file

@ -1,11 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doResolveUris } from 'redux/actions/claims'; import { selectClaimForUri, selectClaimIsMine, selectFetchingMyChannels } from 'redux/selectors/claims';
import {
selectClaimForUri,
makeSelectClaimForUri,
selectClaimIsMine,
selectFetchingMyChannels,
} from 'redux/selectors/claims';
import { import {
selectTopLevelCommentsForUri, selectTopLevelCommentsForUri,
makeSelectTopLevelTotalPagesForUri, makeSelectTopLevelTotalPagesForUri,
@ -28,25 +22,16 @@ const select = (state, props) => {
const { uri } = props; const { uri } = props;
const claim = selectClaimForUri(state, uri); const claim = selectClaimForUri(state, uri);
const channelId = getChannelIdFromClaim(claim);
const activeChannelClaim = selectActiveChannelClaim(state); const activeChannelClaim = selectActiveChannelClaim(state);
const topLevelComments = selectTopLevelCommentsForUri(state, uri);
const resolvedComments =
topLevelComments && topLevelComments.length > 0
? topLevelComments.filter(({ channel_url }) => makeSelectClaimForUri(channel_url)(state) !== undefined)
: [];
return { return {
topLevelComments, topLevelComments: selectTopLevelCommentsForUri(state, uri),
resolvedComments,
allCommentIds: selectCommentIdsForUri(state, uri), allCommentIds: selectCommentIdsForUri(state, uri),
pinnedComments: selectPinnedCommentsForUri(state, uri), pinnedComments: selectPinnedCommentsForUri(state, uri),
topLevelTotalPages: makeSelectTopLevelTotalPagesForUri(uri)(state), topLevelTotalPages: makeSelectTopLevelTotalPagesForUri(uri)(state),
totalComments: makeSelectTotalCommentsCountForUri(uri)(state), totalComments: makeSelectTotalCommentsCountForUri(uri)(state),
claimId: claim && claim.claim_id, claimId: claim && claim.claim_id,
channelId, channelId: getChannelIdFromClaim(claim),
claimIsMine: selectClaimIsMine(state, claim), claimIsMine: selectClaimIsMine(state, claim),
isFetchingComments: selectIsFetchingComments(state), isFetchingComments: selectIsFetchingComments(state),
isFetchingCommentsById: selectIsFetchingCommentsById(state), isFetchingCommentsById: selectIsFetchingCommentsById(state),
@ -64,7 +49,6 @@ const perform = {
fetchComment: doCommentById, fetchComment: doCommentById,
fetchReacts: doCommentReactList, fetchReacts: doCommentReactList,
resetComments: doCommentReset, resetComments: doCommentReset,
doResolveUris,
}; };
export default connect(select, perform)(CommentsList); export default connect(select, perform)(CommentsList);

View file

@ -30,7 +30,6 @@ type Props = {
allCommentIds: any, allCommentIds: any,
pinnedComments: Array<Comment>, pinnedComments: Array<Comment>,
topLevelComments: Array<Comment>, topLevelComments: Array<Comment>,
resolvedComments: Array<Comment>,
topLevelTotalPages: number, topLevelTotalPages: number,
uri: string, uri: string,
claimId?: string, claimId?: string,
@ -47,11 +46,10 @@ type Props = {
activeChannelId: ?string, activeChannelId: ?string,
settingsByChannelId: { [channelId: string]: PerChannelSettings }, settingsByChannelId: { [channelId: string]: PerChannelSettings },
commentsAreExpanded?: boolean, commentsAreExpanded?: boolean,
fetchTopLevelComments: (uri: string, parentId: string, page: number, pageSize: number, sortBy: number) => void, fetchTopLevelComments: (uri: string, parentId: ?string, page: number, pageSize: number, sortBy: number) => void,
fetchComment: (commentId: string) => void, fetchComment: (commentId: string) => void,
fetchReacts: (commentIds: Array<string>) => Promise<any>, fetchReacts: (commentIds: Array<string>) => Promise<any>,
resetComments: (claimId: string) => void, resetComments: (claimId: string) => void,
doResolveUris: (uris: Array<string>, returnCachedClaims: boolean) => void,
}; };
export default function CommentList(props: Props) { export default function CommentList(props: Props) {
@ -60,7 +58,6 @@ export default function CommentList(props: Props) {
uri, uri,
pinnedComments, pinnedComments,
topLevelComments, topLevelComments,
resolvedComments,
topLevelTotalPages, topLevelTotalPages,
claimId, claimId,
channelId, channelId,
@ -79,7 +76,6 @@ export default function CommentList(props: Props) {
fetchComment, fetchComment,
fetchReacts, fetchReacts,
resetComments, resetComments,
doResolveUris,
} = props; } = props;
const isMobile = useIsMobile(); const isMobile = useIsMobile();
@ -89,7 +85,6 @@ export default function CommentList(props: Props) {
const DEFAULT_SORT = ENABLE_COMMENT_REACTIONS ? SORT_BY.POPULARITY : SORT_BY.NEWEST; const DEFAULT_SORT = ENABLE_COMMENT_REACTIONS ? SORT_BY.POPULARITY : SORT_BY.NEWEST;
const [sort, setSort] = usePersistedState('comment-sort-by', DEFAULT_SORT); const [sort, setSort] = usePersistedState('comment-sort-by', DEFAULT_SORT);
const [page, setPage] = React.useState(0); const [page, setPage] = React.useState(0);
const [commentsToDisplay, setCommentsToDisplay] = React.useState(topLevelComments);
const [didInitialPageFetch, setInitialPageFetch] = React.useState(false); const [didInitialPageFetch, setInitialPageFetch] = React.useState(false);
const hasDefaultExpansion = commentsAreExpanded || !isMediumScreen || isMobile; const hasDefaultExpansion = commentsAreExpanded || !isMediumScreen || isMobile;
const [expandedComments, setExpandedComments] = React.useState(hasDefaultExpansion); const [expandedComments, setExpandedComments] = React.useState(hasDefaultExpansion);
@ -97,9 +92,6 @@ export default function CommentList(props: Props) {
const totalFetchedComments = allCommentIds ? allCommentIds.length : 0; const totalFetchedComments = allCommentIds ? allCommentIds.length : 0;
const channelSettings = channelId ? settingsByChannelId[channelId] : undefined; const channelSettings = channelId ? settingsByChannelId[channelId] : undefined;
const moreBelow = page < topLevelTotalPages; const moreBelow = page < topLevelTotalPages;
const isResolvingComments = topLevelComments && resolvedComments.length !== topLevelComments.length;
const alreadyResolved = !isResolvingComments && resolvedComments.length !== 0;
const canDisplayComments = commentsToDisplay && commentsToDisplay.length === topLevelComments.length;
const title = getCommentsListTitle(totalComments); const title = getCommentsListTitle(totalComments);
// Display comments immediately if not fetching reactions // Display comments immediately if not fetching reactions
@ -123,8 +115,7 @@ export default function CommentList(props: Props) {
} }
setPage(1); setPage(1);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps }, [page, claimId, resetComments]);
}, [page, uri, resetComments]); // 'claim' is derived from 'uri'
// Fetch top-level comments // Fetch top-level comments
useEffect(() => { useEffect(() => {
@ -133,7 +124,7 @@ export default function CommentList(props: Props) {
fetchComment(linkedCommentId); fetchComment(linkedCommentId);
} }
fetchTopLevelComments(uri, '', page, COMMENT_PAGE_SIZE_TOP_LEVEL, sort); fetchTopLevelComments(uri, undefined, page, COMMENT_PAGE_SIZE_TOP_LEVEL, sort);
} }
}, [fetchComment, fetchTopLevelComments, linkedCommentId, page, sort, uri]); }, [fetchComment, fetchTopLevelComments, linkedCommentId, page, sort, uri]);
@ -182,6 +173,8 @@ export default function CommentList(props: Props) {
// Infinite scroll // Infinite scroll
useEffect(() => { useEffect(() => {
if (topLevelComments.length === 0) return;
function shouldFetchNextPage(page, topLevelTotalPages, yPrefetchPx = 1000) { function shouldFetchNextPage(page, topLevelTotalPages, yPrefetchPx = 1000) {
if (!spinnerRef || !spinnerRef.current) return false; if (!spinnerRef || !spinnerRef.current) return false;
@ -216,7 +209,7 @@ export default function CommentList(props: Props) {
setInitialPageFetch(true); setInitialPageFetch(true);
} }
if (hasDefaultExpansion && !isFetchingComments && canDisplayComments && readyToDisplayComments && moreBelow) { if (hasDefaultExpansion && !isFetchingComments && readyToDisplayComments && moreBelow) {
const commentsInDrawer = Boolean(document.querySelector('.MuiDrawer-root .card--enable-overflow')); const commentsInDrawer = Boolean(document.querySelector('.MuiDrawer-root .card--enable-overflow'));
const scrollingElement = commentsInDrawer ? document.querySelector('.card--enable-overflow') : window; const scrollingElement = commentsInDrawer ? document.querySelector('.card--enable-overflow') : window;
@ -227,7 +220,7 @@ export default function CommentList(props: Props) {
} }
} }
}, [ }, [
canDisplayComments, topLevelComments,
hasDefaultExpansion, hasDefaultExpansion,
didInitialPageFetch, didInitialPageFetch,
isFetchingComments, isFetchingComments,
@ -238,22 +231,6 @@ export default function CommentList(props: Props) {
topLevelTotalPages, topLevelTotalPages,
]); ]);
// Wait to only display topLevelComments after resolved or else
// other components will try to resolve again, like channelThumbnail
useEffect(() => {
if (!isResolvingComments) setCommentsToDisplay(topLevelComments);
}, [isResolvingComments, topLevelComments]);
// Batch resolve comment channel urls
useEffect(() => {
if (!topLevelComments || alreadyResolved) return;
const urisToResolve = [];
topLevelComments.map(({ channel_url }) => channel_url !== undefined && urisToResolve.push(channel_url));
if (urisToResolve.length > 0) doResolveUris(urisToResolve, true);
}, [alreadyResolved, doResolveUris, topLevelComments]);
const commentProps = { isTopLevel: true, threadDepth: 3, uri, claimIsMine, linkedCommentId }; const commentProps = { isTopLevel: true, threadDepth: 3, uri, claimIsMine, linkedCommentId };
const actionButtonsProps = { totalComments, sort, changeSort, setPage }; const actionButtonsProps = { totalComments, sort, changeSort, setPage };
@ -282,7 +259,7 @@ export default function CommentList(props: Props) {
<> <>
{pinnedComments && <CommentElements comments={pinnedComments} {...commentProps} />} {pinnedComments && <CommentElements comments={pinnedComments} {...commentProps} />}
{commentsToDisplay && <CommentElements comments={commentsToDisplay} {...commentProps} />} <CommentElements comments={topLevelComments} {...commentProps} />
</> </>
)} )}
</ul> </ul>
@ -308,7 +285,7 @@ export default function CommentList(props: Props) {
</div> </div>
)} )}
{(isFetchingComments || (hasDefaultExpansion && moreBelow) || !canDisplayComments) && ( {(isFetchingComments || (hasDefaultExpansion && moreBelow)) && (
<div className="main--empty" ref={spinnerRef}> <div className="main--empty" ref={spinnerRef}>
<Spinner type="small" /> <Spinner type="small" />
</div> </div>

View file

@ -1,26 +1,16 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doResolveUris } from 'redux/actions/claims'; import { selectClaimIsMineForUri } from 'redux/selectors/claims';
import { selectClaimIsMineForUri, makeSelectClaimForUri } from 'redux/selectors/claims';
import { selectIsFetchingCommentsByParentId, selectRepliesForParentId } from 'redux/selectors/comments'; import { selectIsFetchingCommentsByParentId, selectRepliesForParentId } from 'redux/selectors/comments';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import CommentsReplies from './view'; import CommentsReplies from './view';
const select = (state, props) => { const select = (state, props) => {
const fetchedReplies = selectRepliesForParentId(state, props.parentId); const { uri, parentId } = props;
const resolvedReplies =
fetchedReplies && fetchedReplies.length > 0
? fetchedReplies.filter(({ channel_url }) => makeSelectClaimForUri(channel_url)(state) !== undefined)
: [];
return { return {
fetchedReplies, fetchedReplies: selectRepliesForParentId(state, parentId),
resolvedReplies, claimIsMine: selectClaimIsMineForUri(state, uri),
claimIsMine: selectClaimIsMineForUri(state, props.uri), isFetching: selectIsFetchingCommentsByParentId(state, parentId),
userCanComment: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
isFetchingByParentId: selectIsFetchingCommentsByParentId(state),
}; };
}; };
const perform = (dispatch) => ({ doResolveUris: (uris) => dispatch(doResolveUris(uris, true)) }); export default connect(select)(CommentsReplies);
export default connect(select, perform)(CommentsReplies);

View file

@ -6,61 +6,34 @@ import React from 'react';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
type Props = { type Props = {
fetchedReplies: Array<Comment>,
resolvedReplies: Array<Comment>,
uri: string, uri: string,
parentId: string,
claimIsMine: boolean,
linkedCommentId?: string, linkedCommentId?: string,
userCanComment: boolean,
threadDepth: number, threadDepth: number,
numDirectReplies: number, // Total replies for parentId as reported by 'comment[replies]'. Includes blocked items. numDirectReplies: number, // Total replies for parentId as reported by 'comment[replies]'. Includes blocked items.
isFetchingByParentId: { [string]: boolean },
hasMore: boolean, hasMore: boolean,
supportDisabled: boolean, supportDisabled: boolean,
doResolveUris: (Array<string>) => void,
onShowMore?: () => void, onShowMore?: () => void,
// redux
fetchedReplies: Array<Comment>,
claimIsMine: boolean,
isFetching: boolean,
}; };
function CommentsReplies(props: Props) { export default function CommentsReplies(props: Props) {
const { const {
uri, uri,
parentId,
fetchedReplies, fetchedReplies,
resolvedReplies,
claimIsMine, claimIsMine,
linkedCommentId, linkedCommentId,
userCanComment,
threadDepth, threadDepth,
numDirectReplies, numDirectReplies,
isFetchingByParentId, isFetching,
hasMore, hasMore,
supportDisabled, supportDisabled,
doResolveUris,
onShowMore, onShowMore,
} = props; } = props;
const [isExpanded, setExpanded] = React.useState(true); const [isExpanded, setExpanded] = React.useState(true);
const [commentsToDisplay, setCommentsToDisplay] = React.useState(fetchedReplies);
const isResolvingReplies = fetchedReplies && resolvedReplies.length !== fetchedReplies.length;
const alreadyResolved = !isResolvingReplies && resolvedReplies.length !== 0;
const canDisplayComments = commentsToDisplay && commentsToDisplay.length === fetchedReplies.length;
// Batch resolve comment channel urls
React.useEffect(() => {
if (!fetchedReplies || alreadyResolved) return;
const urisToResolve = [];
fetchedReplies.map(({ channel_url }) => channel_url !== undefined && urisToResolve.push(channel_url));
if (urisToResolve.length > 0) doResolveUris(urisToResolve);
}, [alreadyResolved, doResolveUris, fetchedReplies]);
// Wait to only display topLevelComments after resolved or else
// other components will try to resolve again, like channelThumbnail
React.useEffect(() => {
if (!isResolvingReplies) setCommentsToDisplay(fetchedReplies);
}, [isResolvingReplies, fetchedReplies]);
return !numDirectReplies ? null : ( return !numDirectReplies ? null : (
<div className="comment__replies-container"> <div className="comment__replies-container">
@ -78,24 +51,21 @@ function CommentsReplies(props: Props) {
<Button className="comment__threadline" aria-label="Hide Replies" onClick={() => setExpanded(false)} /> <Button className="comment__threadline" aria-label="Hide Replies" onClick={() => setExpanded(false)} />
<ul className="comments--replies"> <ul className="comments--replies">
{!isResolvingReplies && {fetchedReplies.map((comment) => (
commentsToDisplay && <Comment
commentsToDisplay.length > 0 && key={comment.comment_id}
commentsToDisplay.map((comment) => ( threadDepth={threadDepth}
<Comment uri={uri}
key={comment.comment_id} comment={comment}
threadDepth={threadDepth} claimIsMine={claimIsMine}
uri={uri} linkedCommentId={linkedCommentId}
comment={comment} supportDisabled={supportDisabled}
claimIsMine={claimIsMine} />
linkedCommentId={linkedCommentId} ))}
commentingEnabled={userCanComment}
supportDisabled={supportDisabled}
/>
))}
</ul> </ul>
</div> </div>
)} )}
{isExpanded && fetchedReplies && hasMore && ( {isExpanded && fetchedReplies && hasMore && (
<div className="comment__actions--nested"> <div className="comment__actions--nested">
<Button <Button
@ -106,7 +76,8 @@ function CommentsReplies(props: Props) {
/> />
</div> </div>
)} )}
{(isFetchingByParentId[parentId] || isResolvingReplies || !canDisplayComments) && (
{isFetching && (
<div className="comment__replies-container"> <div className="comment__replies-container">
<div className="comment__actions--nested"> <div className="comment__actions--nested">
<Spinner type="small" /> <Spinner type="small" />
@ -116,5 +87,3 @@ function CommentsReplies(props: Props) {
</div> </div>
); );
} }
export default CommentsReplies;

View file

@ -19,6 +19,7 @@ import {
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications'; import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
import { selectActiveChannelClaim } from 'redux/selectors/app'; import { selectActiveChannelClaim } from 'redux/selectors/app';
import { toHex } from 'util/hex'; import { toHex } from 'util/hex';
import { getChannelFromClaim } from 'util/claim';
import Comments from 'comments'; import Comments from 'comments';
import { selectPrefsReady } from 'redux/selectors/sync'; import { selectPrefsReady } from 'redux/selectors/sync';
import { doAlertWaitingForSync } from 'redux/actions/app'; import { doAlertWaitingForSync } from 'redux/actions/app';
@ -30,47 +31,41 @@ const MENTION_REGEX = /(?:^| |\n)@[^\s=&#$@%?:;/"<>%{}|^~[]*(?::[\w]+)?/gm;
export function doCommentList( export function doCommentList(
uri: string, uri: string,
parentId: string, parentId: ?string,
page: number = 1, page: number = 1,
pageSize: number = 99999, pageSize: number = 99999,
sortBy: number = SORT_BY.NEWEST sortBy: number = SORT_BY.NEWEST
) { ) {
return (dispatch: Dispatch, getState: GetState) => { return (dispatch: Dispatch, getState: GetState) => {
const state = getState(); const state = getState();
const claim = selectClaimsByUri(state)[uri]; const claim = selectClaimForUri(state, uri);
const claimId = claim ? claim.claim_id : null; const { claim_id: claimId } = claim || {};
if (!claimId) { if (!claimId) {
dispatch({ return dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: 'unable to find claim for uri' });
type: ACTIONS.COMMENT_LIST_FAILED,
data: 'unable to find claim for uri',
});
return;
} }
dispatch({ dispatch({ type: ACTIONS.COMMENT_LIST_STARTED, data: { parentId } });
type: ACTIONS.COMMENT_LIST_STARTED,
data: {
parentId,
},
});
// Adding 'channel_id' and 'channel_name' enables "CreatorSettings > commentsEnabled". // Adding 'channel_id' and 'channel_name' enables "CreatorSettings > commentsEnabled".
const creatorChannelClaim = claim.value_type === 'channel' ? claim : claim.signing_channel; const creatorChannelClaim = getChannelFromClaim(claim);
const { claim_id: creatorClaimId, name: channelName } = creatorChannelClaim || {};
return Comments.comment_list({ return Comments.comment_list({
page, page,
claim_id: claimId, claim_id: claimId,
page_size: pageSize, page_size: pageSize,
parent_id: parentId || undefined, parent_id: parentId,
top_level: !parentId, top_level: !parentId,
channel_id: creatorChannelClaim ? creatorChannelClaim.claim_id : undefined, channel_id: creatorClaimId,
channel_name: creatorChannelClaim ? creatorChannelClaim.name : undefined, channel_name: channelName,
sort_by: sortBy, sort_by: sortBy,
}) })
.then((result: CommentListResponse) => { .then((result: CommentListResponse) => {
const { items: comments, total_items, total_filtered_items, total_pages } = result; const { items: comments, total_items, total_filtered_items, total_pages } = result;
dispatch({
const commentChannelUrls = comments && comments.map((comment) => comment.channel_url || '');
const dispatchData = {
type: ACTIONS.COMMENT_LIST_COMPLETED, type: ACTIONS.COMMENT_LIST_COMPLETED,
data: { data: {
comments, comments,
@ -78,38 +73,36 @@ export function doCommentList(
totalItems: total_items, totalItems: total_items,
totalFilteredItems: total_filtered_items, totalFilteredItems: total_filtered_items,
totalPages: total_pages, totalPages: total_pages,
claimId: claimId, claimId,
creatorClaimId: creatorChannelClaim ? creatorChannelClaim.claim_id : undefined, creatorClaimId,
uri: uri, uri,
}, },
}); };
return result; // Batch resolve comment channel urls
if (commentChannelUrls) {
return dispatch(async () => await doResolveUris(commentChannelUrls, true)).then(() => {
dispatch({ ...dispatchData });
return result;
});
} else {
dispatch({ ...dispatchData });
return result;
}
}) })
.catch((error) => { .catch((error) => {
switch (error.message) { const { message } = error;
switch (message) {
case 'comments are disabled by the creator': case 'comments are disabled by the creator':
dispatch({ return dispatch({ type: ACTIONS.COMMENT_LIST_COMPLETED, data: { creatorClaimId, disabled: true } });
type: ACTIONS.COMMENT_LIST_COMPLETED,
data: {
creatorClaimId: creatorChannelClaim ? creatorChannelClaim.claim_id : undefined,
disabled: true,
},
});
break;
case FETCH_API_FAILED_TO_FETCH: case FETCH_API_FAILED_TO_FETCH:
dispatch( dispatch(doToast({ isError: true, message: __('Failed to fetch comments.') }));
doToast({ return dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error });
isError: true,
message: __('Failed to fetch comments.'),
})
);
dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error });
break;
default: default:
dispatch(doToast({ isError: true, message: `${error.message}` })); dispatch(doToast({ isError: true, message: `${message}` }));
dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error }); dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error });
} }
}); });

View file

@ -24,7 +24,6 @@ export const selectCommentsById = (state: State) => selectState(state).commentBy
export const selectCommentIdsByClaimId = (state: State) => selectState(state).byId; export const selectCommentIdsByClaimId = (state: State) => selectState(state).byId;
export const selectIsFetchingComments = (state: State) => selectState(state).isLoading; export const selectIsFetchingComments = (state: State) => selectState(state).isLoading;
export const selectIsFetchingCommentsById = (state: State) => selectState(state).isLoadingById; export const selectIsFetchingCommentsById = (state: State) => selectState(state).isLoadingById;
export const selectIsFetchingCommentsByParentId = (state: State) => selectState(state).isLoadingByParentId;
export const selectIsFetchingReacts = (state: State) => selectState(state).isFetchingReacts; export const selectIsFetchingReacts = (state: State) => selectState(state).isFetchingReacts;
export const selectMyReacts = (state: State) => state.comments.myReactsByCommentId; export const selectMyReacts = (state: State) => state.comments.myReactsByCommentId;
@ -32,6 +31,9 @@ export const selectMyReactsForComment = (state: State, commentIdChannelId: strin
// @commentIdChannelId: Format = 'commentId:MyChannelId' // @commentIdChannelId: Format = 'commentId:MyChannelId'
return state.comments.myReactsByCommentId && state.comments.myReactsByCommentId[commentIdChannelId]; return state.comments.myReactsByCommentId && state.comments.myReactsByCommentId[commentIdChannelId];
}; };
export const selectIsFetchingCommentsByParentId = (state: State, parentId: string) => {
return selectState(state).isLoadingByParentId[parentId];
};
export const selectOthersReacts = (state: State) => state.comments.othersReactsByCommentId; export const selectOthersReacts = (state: State) => state.comments.othersReactsByCommentId;
export const selectOthersReactsForComment = (state: State, id: string) => { export const selectOthersReactsForComment = (state: State, id: string) => {