diff --git a/web/avatars/avatar.react.js b/web/avatars/avatar.react.js
--- a/web/avatars/avatar.react.js
+++ b/web/avatars/avatar.react.js
@@ -10,6 +10,7 @@
 
 import css from './avatar.css';
 import LoadingIndicator from '../loading-indicator.react.js';
+import EncryptedMultimedia from '../media/encrypted-multimedia.react.js';
 
 type Props = {
   +avatarInfo: ResolvedClientAvatar,
@@ -20,6 +21,26 @@
 function Avatar(props: Props): React.Node {
   const { avatarInfo, size, showSpinner } = props;
 
+  let loadingIndicatorSize;
+  if (size === 'XS') {
+    loadingIndicatorSize = 'small';
+  } else if (size === 'S') {
+    loadingIndicatorSize = 'small';
+  } else if (size === 'M') {
+    loadingIndicatorSize = 'medium';
+  } else {
+    loadingIndicatorSize = 'large';
+  }
+
+  const loadingIndicator = React.useMemo(
+    () => (
+      <div className={css.editAvatarLoadingSpinner}>
+        <LoadingIndicator status="loading" size={loadingIndicatorSize} />
+      </div>
+    ),
+    [loadingIndicatorSize],
+  );
+
   const containerSizeClassName = classnames({
     [css.imgContainer]: avatarInfo.type === 'image',
     [css.xSmall]: size === 'XS',
@@ -56,6 +77,17 @@
           className={containerSizeClassName}
         />
       );
+    } else if (avatarInfo.type === 'encrypted_image') {
+      return (
+        <EncryptedMultimedia
+          type="encrypted_photo"
+          blobURI={avatarInfo.blobURI}
+          encryptionKey={avatarInfo.encryptionKey}
+          multimediaClassName={containerSizeClassName}
+          loadingIndicatorComponent={loadingIndicator}
+          invisibleLoad={showSpinner}
+        />
+      );
     }
 
     return (
@@ -67,31 +99,15 @@
     avatarInfo.emoji,
     avatarInfo.type,
     avatarInfo.uri,
+    avatarInfo.blobURI,
+    avatarInfo.encryptionKey,
+    showSpinner,
+    loadingIndicator,
     containerSizeClassName,
     emojiContainerColorStyle,
     emojiSizeClassName,
   ]);
 
-  let loadingIndicatorSize;
-  if (size === 'XS') {
-    loadingIndicatorSize = 'small';
-  } else if (size === 'S') {
-    loadingIndicatorSize = 'small';
-  } else if (size === 'M') {
-    loadingIndicatorSize = 'medium';
-  } else {
-    loadingIndicatorSize = 'large';
-  }
-
-  const loadingIndicator = React.useMemo(
-    () => (
-      <div className={css.editAvatarLoadingSpinner}>
-        <LoadingIndicator status="loading" size={loadingIndicatorSize} />
-      </div>
-    ),
-    [loadingIndicatorSize],
-  );
-
   return (
     <div className={css.avatarContainer}>
       {showSpinner ? loadingIndicator : null}
diff --git a/web/media/encrypted-multimedia.react.js b/web/media/encrypted-multimedia.react.js
--- a/web/media/encrypted-multimedia.react.js
+++ b/web/media/encrypted-multimedia.react.js
@@ -23,6 +23,10 @@
   +placeholderSrc?: ?string,
   +multimediaClassName?: string,
   +elementStyle?: ?Shape<CSSStyle>,
+  // if provided, this component will be shown instead of the loading indicator
+  +loadingIndicatorComponent?: React.Node,
+  // if true, the loading indicator will not be shown
+  +invisibleLoad?: boolean,
 };
 
 function EncryptedMultimedia(props: Props): React.Node {
@@ -32,6 +36,7 @@
     placeholderSrc,
     elementStyle,
     multimediaClassName,
+    invisibleLoad,
   } = props;
 
   const [source, setSource] = React.useState(null);
@@ -78,8 +83,8 @@
 
   let loadingIndicator, errorIndicator;
 
-  if (!source) {
-    loadingIndicator = (
+  if (!source && !invisibleLoad) {
+    loadingIndicator = props.loadingIndicatorComponent ?? (
       <LoadingIndicator
         status="loading"
         size="large"
diff --git a/web/package.json b/web/package.json
--- a/web/package.json
+++ b/web/package.json
@@ -106,7 +106,7 @@
       "\\.js$": "babel-jest"
     },
     "transformIgnorePatterns": [
-      "/node_modules/(?!@babel/runtime)"
+      "/node_modules/(?!(@babel/runtime|thumbhash))"
     ],
     "moduleNameMapper": {
       "\\.(css)$": "identity-obj-proxy"