disable while reacting, dont call api twice

This commit is contained in:
jessop 2020-09-29 14:45:28 -04:00 committed by Sean Yesmunt
parent 63ce107cc1
commit ad88f7de7f
7 changed files with 92 additions and 21 deletions

View file

@ -25,6 +25,7 @@ declare type CommentsState = {
isFetchingReacts: boolean, isFetchingReacts: boolean,
myReactsByCommentId: any, myReactsByCommentId: any,
othersReactsByCommentId: any, othersReactsByCommentId: any,
typesReacting: Array<string>,
}; };
declare type CommentReactParams = { declare type CommentReactParams = {

View file

@ -1297,5 +1297,6 @@
"Credit Details": "Credit Details", "Credit Details": "Credit Details",
"Sign In": "Sign In", "Sign In": "Sign In",
"Change to tile layout": "Change to tile layout", "Change to tile layout": "Change to tile layout",
"%total_comments% comments": "%total_comments% comments",
"--end--": "--end--" "--end--": "--end--"
} }

View file

@ -1,11 +1,16 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Comment from './view'; import Comment from './view';
import { makeSelectMyReactionsForComment, makeSelectOthersReactionsForComment } from 'redux/selectors/comments'; import {
makeSelectMyReactionsForComment,
makeSelectOthersReactionsForComment,
selectTypesReacting,
} from 'redux/selectors/comments';
import { doCommentReact } from 'redux/actions/comments'; import { doCommentReact } from 'redux/actions/comments';
const select = (state, props) => ({ const select = (state, props) => ({
myReacts: makeSelectMyReactionsForComment(props.commentId)(state), myReacts: makeSelectMyReactionsForComment(props.commentId)(state),
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state), othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
typesReacting: selectTypesReacting(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -11,10 +11,11 @@ type Props = {
othersReacts: any, othersReacts: any,
react: (string, string) => void, react: (string, string) => void,
commentId: string, commentId: string,
typesReacting: Array<string>,
}; };
export default function CommentReactions(props: Props) { export default function CommentReactions(props: Props) {
const { myReacts, othersReacts, commentId, react } = props; const { myReacts, othersReacts, commentId, react, typesReacting } = props;
const [activeChannel] = usePersistedState('comment-channel'); const [activeChannel] = usePersistedState('comment-channel');
const getCountForReact = type => { const getCountForReact = type => {
@ -36,7 +37,7 @@ export default function CommentReactions(props: Props) {
className={classnames('comment__action', { className={classnames('comment__action', {
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.LIKE), 'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.LIKE),
})} })}
disabled={!activeChannel} disabled={!activeChannel || typesReacting.includes(REACTION_TYPES.LIKE)}
onClick={() => react(commentId, REACTION_TYPES.LIKE)} onClick={() => react(commentId, REACTION_TYPES.LIKE)}
label={getCountForReact(REACTION_TYPES.LIKE)} label={getCountForReact(REACTION_TYPES.LIKE)}
/> />
@ -46,6 +47,7 @@ export default function CommentReactions(props: Props) {
className={classnames('comment__action', { className={classnames('comment__action', {
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.DISLIKE), 'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.DISLIKE),
})} })}
disabled={!activeChannel || typesReacting.includes(REACTION_TYPES.DISLIKE)}
onClick={() => react(commentId, REACTION_TYPES.DISLIKE)} onClick={() => react(commentId, REACTION_TYPES.DISLIKE)}
label={getCountForReact(REACTION_TYPES.DISLIKE)} label={getCountForReact(REACTION_TYPES.DISLIKE)}
/> />

View file

@ -3,7 +3,11 @@ import * as ACTIONS from 'constants/action_types';
import * as REACTION_TYPES from 'constants/reactions'; import * as REACTION_TYPES from 'constants/reactions';
import { Lbry, selectClaimsByUri, selectMyChannelClaims } from 'lbry-redux'; import { Lbry, selectClaimsByUri, selectMyChannelClaims } from 'lbry-redux';
import { doToast } from 'redux/actions/notifications'; import { doToast } from 'redux/actions/notifications';
import { makeSelectCommentIdsForUri, makeSelectMyReactionsForComment } from 'redux/selectors/comments'; import {
makeSelectCommentIdsForUri,
makeSelectMyReactionsForComment,
makeSelectOthersReactionsForComment,
} from 'redux/selectors/comments';
export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) { export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) {
return (dispatch: Dispatch, getState: GetState) => { return (dispatch: Dispatch, getState: GetState) => {
@ -45,7 +49,6 @@ export function doCommentReactList(uri: string | null, commentId?: string) {
return (dispatch: Dispatch, getState: GetState) => { return (dispatch: Dispatch, getState: GetState) => {
const state = getState(); const state = getState();
const channel = localStorage.getItem('comment-channel'); const channel = localStorage.getItem('comment-channel');
// if not channel, fail?
if (!channel) { if (!channel) {
dispatch({ dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_FAILED, type: ACTIONS.COMMENT_REACTION_LIST_FAILED,
@ -88,7 +91,6 @@ export function doCommentReact(commentId: string, type: string) {
return (dispatch: Dispatch, getState: GetState) => { return (dispatch: Dispatch, getState: GetState) => {
const state = getState(); const state = getState();
const channel = localStorage.getItem('comment-channel'); const channel = localStorage.getItem('comment-channel');
// if not channel, fail?
if (!channel) { if (!channel) {
dispatch({ dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_FAILED, type: ACTIONS.COMMENT_REACTION_LIST_FAILED,
@ -97,7 +99,9 @@ export function doCommentReact(commentId: string, type: string) {
return; return;
} }
const myChannels = selectMyChannelClaims(state); const myChannels = selectMyChannelClaims(state);
const myReacts = makeSelectMyReactionsForComment(commentId)(state); let myReacts = makeSelectMyReactionsForComment(commentId)(state);
let reactingTypes = [];
const othersReacts = makeSelectOthersReactionsForComment(commentId)(state);
const claimForChannelName = myChannels.find(chan => chan.name === channel); const claimForChannelName = myChannels.find(chan => chan.name === channel);
const channelId = claimForChannelName && claimForChannelName.claim_id; const channelId = claimForChannelName && claimForChannelName.claim_id;
const exclusiveTypes = { const exclusiveTypes = {
@ -105,33 +109,55 @@ export function doCommentReact(commentId: string, type: string) {
[REACTION_TYPES.DISLIKE]: REACTION_TYPES.LIKE, [REACTION_TYPES.DISLIKE]: REACTION_TYPES.LIKE,
}; };
dispatch({
type: ACTIONS.COMMENT_REACT_STARTED,
});
const params: CommentReactParams = { const params: CommentReactParams = {
comment_ids: commentId, comment_ids: commentId,
channel_name: channel, channel_name: channel,
channel_id: channelId, channel_id: channelId,
react_type: type, react_type: type,
}; };
if (Object.keys(exclusiveTypes).includes(type)) {
params['clear_types'] = exclusiveTypes[type];
}
if (myReacts.includes(type)) { if (myReacts.includes(type)) {
params['remove'] = true; params['remove'] = true;
myReacts.splice(myReacts.indexOf(type), 1);
reactingTypes.push(type);
} else {
myReacts.push(type);
reactingTypes.push(type);
if (Object.keys(exclusiveTypes).includes(type)) {
params['clear_types'] = exclusiveTypes[type];
reactingTypes.push(exclusiveTypes[type]);
if (myReacts.indexOf(exclusiveTypes[type]) !== -1) {
myReacts.splice(myReacts.indexOf(exclusiveTypes[type]), 1);
}
}
} }
dispatch({
type: ACTIONS.COMMENT_REACT_STARTED,
data: reactingTypes,
});
// simulate api return shape: ['like'] -> { 'like': 1 }
const myReactsObj = myReacts.reduce((acc, el) => {
acc[el] = 1;
return acc;
}, {});
Lbry.comment_react(params) Lbry.comment_react(params)
.then((result: CommentReactListResponse) => { .then((result: CommentReactListResponse) => {
dispatch({ dispatch({
type: ACTIONS.COMMENT_REACT_COMPLETED, type: ACTIONS.COMMENT_REACT_COMPLETED,
data: reactingTypes,
});
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_COMPLETED,
data: {
myReactions: { [commentId]: myReactsObj },
othersReactions: { [commentId]: othersReacts },
},
}); });
dispatch(doCommentReactList(null, commentId));
}) })
.catch(error => { .catch(error => {
dispatch({ dispatch({
type: ACTIONS.COMMENT_REACT_FAILED, type: ACTIONS.COMMENT_REACT_FAILED,
data: error, data: reactingTypes,
}); });
}); });
}; };

View file

@ -12,6 +12,7 @@ const defaultState: CommentsState = {
isCommenting: false, isCommenting: false,
myComments: undefined, myComments: undefined,
isFetchingReacts: false, isFetchingReacts: false,
typesReacting: [],
myReactsByCommentId: {}, myReactsByCommentId: {},
othersReactsByCommentId: {}, othersReactsByCommentId: {},
}; };
@ -81,23 +82,56 @@ export default handleActions(
isFetchingReacts: false, isFetchingReacts: false,
}), }),
[ACTIONS.COMMENT_REACT_FAILED]: (state: CommentsState, action: any): CommentsState => {
return {
...state,
typesReacting: [],
};
},
[ACTIONS.COMMENT_REACT_STARTED]: (state: CommentsState, action: any): CommentsState => {
const reactingTypes = action.data;
const newReactingTypes = new Set(state.typesReacting);
reactingTypes.forEach(type => {
newReactingTypes.add(type);
});
return {
...state,
typesReacting: Array.from(newReactingTypes),
};
},
[ACTIONS.COMMENT_REACT_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
const reactingTypes = action.data;
const newReactingTypes = new Set(state.typesReacting);
reactingTypes.forEach(type => {
newReactingTypes.delete(type);
});
return {
...state,
typesReacting: Array.from(newReactingTypes),
};
},
[ACTIONS.COMMENT_REACTION_LIST_COMPLETED]: (state: CommentsState, action: any): CommentsState => { [ACTIONS.COMMENT_REACTION_LIST_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
const { myReactions, othersReactions } = action.data; const { myReactions, othersReactions } = action.data;
const myReacts = Object.assign({}, state.myReactsByCommentId); const myReacts = Object.assign({}, state.myReactsByCommentId);
const othersReacts = Object.assign({}, state.othersReactsByCommentId); const othersReacts = Object.assign({}, state.othersReactsByCommentId);
if (myReactions) { if (myReactions) {
Object.entries(myReactions).forEach(e => { Object.entries(myReactions).forEach(([commentId, reactions]) => {
myReacts[e[0]] = Object.entries(e[1]).reduce((acc, el) => { myReacts[commentId] = Object.entries(reactions).reduce((acc, [name, count]) => {
if (el[1] === 1) { if (count === 1) {
acc.push(el[0]); acc.push(name);
} }
return acc; return acc;
}, []); }, []);
}); });
} }
if (othersReactions) { if (othersReactions) {
Object.entries(othersReactions).forEach(e => { Object.entries(othersReactions).forEach(([commentId, reactions]) => {
othersReacts[e[0]] = e[1]; othersReacts[commentId] = reactions;
}); });
} }

View file

@ -104,6 +104,8 @@ export const makeSelectOthersReactionsForComment = (commentId: string) =>
return state.othersReactsByCommentId[commentId]; return state.othersReactsByCommentId[commentId];
}); });
export const selectTypesReacting = createSelector(selectState, state => state.typesReacting);
export const makeSelectCommentsForUri = (uri: string) => export const makeSelectCommentsForUri = (uri: string) =>
createSelector( createSelector(
selectCommentsByClaimId, selectCommentsByClaimId,