diff --git a/apps/comments-ui/src/App.js b/apps/comments-ui/src/App.js
index b7385043cd..7e1140db53 100644
--- a/apps/comments-ui/src/App.js
+++ b/apps/comments-ui/src/App.js
@@ -1,6 +1,6 @@
import Frame from './components/Frame';
import React from 'react';
-import ActionHandler from './actions';
+import {isSyncAction, ActionHandler, SyncActionHandler} from './actions';
import {createPopupNotification} from './utils/helpers';
import AppContext from './AppContext';
import {hasMode} from './utils/check-mode';
@@ -51,10 +51,14 @@ export default class App extends React.Component {
customSiteUrl: props.customSiteUrl,
postId: props.postId,
popup: null,
- accentColor: props.accentColor
+ accentColor: props.accentColor,
+ secundaryFormCount: 0
};
this.adminApi = null;
this.GhostApi = null;
+
+ // Bind to make sure we have a variable reference (and don't need to create a new binded function in our context value every time the state changes)
+ this.dispatchAction = this.dispatchAction.bind(this);
}
/** Initialize comments setup on load, fetch data and setup state*/
@@ -111,6 +115,15 @@ export default class App extends React.Component {
/** Handle actions from across App and update App state */
async dispatchAction(action, data) {
+ if (isSyncAction(action)) {
+ // Makes sure we correctly handle the old state
+ // because updates to state may be asynchronous
+ // so calling dispatchAction('counterUp') multiple times, may yield unexpected results if we don't use a callback function
+ this.setState((state) => {
+ return SyncActionHandler({action, data, state, api: this.GhostApi, adminApi: this.adminApi});
+ });
+ return;
+ }
clearTimeout(this.timeoutId);
this.setState({
action: `${action}:running`
@@ -252,7 +265,7 @@ export default class App extends React.Component {
/**Get final App level context from App state*/
getContextFromState() {
- const {action, popupNotification, customSiteUrl, member, comments, pagination, commentCount, postId, admin, popup} = this.state;
+ const {action, popupNotification, customSiteUrl, member, comments, pagination, commentCount, postId, admin, popup, secundaryFormCount} = this.state;
return {
action,
popupNotification,
@@ -272,14 +285,12 @@ export default class App extends React.Component {
appVersion: this.props.appVersion,
stylesUrl: this.props.stylesUrl,
publication: this.props.publication,
+ secundaryFormCount: secundaryFormCount,
popup,
- dispatchAction: (_action, data) => this.dispatchAction(_action, data),
- /**
- * @deprecated
- * Use dispatchAction instead
- */
- onAction: (_action, data) => this.dispatchAction(_action, data)
+ // Warning: make sure we pass a variable here (binded in constructor), because if we create a new function here, it will also change when anything in the state changes
+ // causing loops in useEffect hooks that depend on dispatchAction
+ dispatchAction: this.dispatchAction
};
}
diff --git a/apps/comments-ui/src/AppContext.js b/apps/comments-ui/src/AppContext.js
index 785b5f452d..8a1f73897a 100644
--- a/apps/comments-ui/src/AppContext.js
+++ b/apps/comments-ui/src/AppContext.js
@@ -1,15 +1,6 @@
// Ref: https://reactjs.org/docs/context.html
const React = require('react');
-const AppContext = React.createContext({
- site: {},
- member: {},
- action: '',
- brandColor: '',
- pageData: {},
- onAction: (action, data) => {
- return {action, data};
- }
-});
+const AppContext = React.createContext({});
export default AppContext;
diff --git a/apps/comments-ui/src/actions.js b/apps/comments-ui/src/actions.js
index 8cb8e0bf44..b3b84e3761 100644
--- a/apps/comments-ui/src/actions.js
+++ b/apps/comments-ui/src/actions.js
@@ -332,6 +332,26 @@ function closePopup() {
};
}
+function increaseSecundaryFormCount({state}) {
+ return {
+ secundaryFormCount: state.secundaryFormCount + 1
+ };
+}
+
+function decreaseSecundaryFormCount({state}) {
+ return {
+ secundaryFormCount: state.secundaryFormCount - 1
+ };
+}
+
+// Sync actions make use of setState((currentState) => newState), to avoid 'race' conditions
+const SyncActions = {
+ openPopup,
+ closePopup,
+ increaseSecundaryFormCount,
+ decreaseSecundaryFormCount
+};
+
const Actions = {
// Put your actions here
addComment,
@@ -345,16 +365,28 @@ const Actions = {
addReply,
loadMoreComments,
loadMoreReplies,
- updateMember,
- openPopup,
- closePopup
+ updateMember
};
+export function isSyncAction(action) {
+ return !!SyncActions[action];
+}
+
/** Handle actions in the App, returns updated state */
-export default async function ActionHandler({action, data, state, api, adminApi}) {
+export async function ActionHandler({action, data, state, api, adminApi}) {
const handler = Actions[action];
if (handler) {
return await handler({data, state, api, adminApi}) || {};
}
return {};
}
+
+/** Handle actions in the App, returns updated state */
+export function SyncActionHandler({action, data, state, api, adminApi}) {
+ const handler = SyncActions[action];
+ if (handler) {
+ // Do not await here
+ return handler({data, state, api, adminApi}) || {};
+ }
+ return {};
+}
diff --git a/apps/comments-ui/src/components/Avatar.js b/apps/comments-ui/src/components/Avatar.js
index 7405578043..e421c7864a 100644
--- a/apps/comments-ui/src/components/Avatar.js
+++ b/apps/comments-ui/src/components/Avatar.js
@@ -3,9 +3,23 @@ import AppContext from '../AppContext';
import {getInitials} from '../utils/helpers';
import {ReactComponent as AvatarIcon} from '../images/icons/avatar.svg';
-const Avatar = (props) => {
- const {member} = useContext(AppContext);
- const memberName = member?.name ?? props.comment?.member?.name;
+const Avatar = ({comment, size, isBlank}) => {
+ const {member, avatarSaturation} = useContext(AppContext);
+ const dimensionClasses = (size === 'small' ? 'w-6 h-6 sm:w-8 sm:h-8' : 'w-9 h-9 sm:w-[40px] sm:h-[40px]');
+
+ // When an avatar has been deleted or hidden
+ // todo: move to seperate component
+ if (isBlank) {
+ return (
+
+ );
+ }
+
+ const memberName = member?.name ?? comment?.member?.name;
const getHashOfString = (str) => {
let hash = 0;
@@ -21,13 +35,13 @@ const Avatar = (props) => {
};
const generateHSL = () => {
- let commentMember = (props.comment ? props.comment.member : member);
+ let commentMember = (comment ? comment.member : member);
if (!commentMember || !commentMember.name) {
return [0,0,10];
}
- const saturation = isNaN(props.saturation) ? 50 : props.saturation;
+ const saturation = isNaN(avatarSaturation) ? 50 : avatarSaturation;
const hRange = [0, 360];
const lRangeTop = Math.round(saturation / (100 / 30)) + 30;
@@ -46,11 +60,11 @@ const Avatar = (props) => {
};
const commentGetInitials = () => {
- if (props.comment && !props.comment.member) {
+ if (comment && !comment.member) {
return getInitials('Deleted member');
}
- let commentMember = (props.comment ? props.comment.member : member);
+ let commentMember = (comment ? comment.member : member);
if (!commentMember || !commentMember.name) {
return getInitials('Anonymous');
@@ -58,9 +72,8 @@ const Avatar = (props) => {
return getInitials(commentMember.name);
};
- let dimensionClasses = (props.size === 'small' ? 'w-6 h-6 sm:w-8 sm:h-8' : 'w-9 h-9 sm:w-[40px] sm:h-[40px]');
- let initialsClasses = (props.size === 'small' ? 'text-sm' : 'text-lg');
- let commentMember = (props.comment ? props.comment.member : member);
+ let initialsClasses = (size === 'small' ? 'text-sm' : 'text-lg');
+ let commentMember = (comment ? comment.member : member);
const bgColor = HSLtoString(generateHSL());
const avatarStyle = {
@@ -80,15 +93,6 @@ const Avatar = (props) => {
>
);
- // When an avatar has been deleted or hidden
- if (props.isBlank) {
- avatarEl = (
-
- );
- }
-
return (