diff --git a/native/media/encrypted-image.react.js b/native/media/encrypted-image.react.js
--- a/native/media/encrypted-image.react.js
+++ b/native/media/encrypted-image.react.js
@@ -4,7 +4,7 @@
 
 import { MediaCacheContext } from 'lib/components/media-cache-provider.react.js';
 
-import { decryptMedia } from './encryption-utils.js';
+import { decryptBase64, decryptMedia } from './encryption-utils.js';
 import LoadableImage from './loadable-image.react.js';
 import { useSelector } from '../redux/redux-utils.js';
 import type { ImageStyle } from '../types/styles.js';
@@ -16,13 +16,19 @@
   +spinnerColor: string,
   +style: ImageStyle,
   +invisibleLoad: boolean,
+  +thumbHash?: ?string,
 };
 type Props = {
   ...BaseProps,
 };
 
 function EncryptedImage(props: Props): React.Node {
-  const { holder, encryptionKey, onLoad: onLoadProp } = props;
+  const {
+    holder,
+    encryptionKey,
+    onLoad: onLoadProp,
+    thumbHash: encryptedThumbHash,
+  } = props;
 
   const mediaCache = React.useContext(MediaCacheContext);
   const [source, setSource] = React.useState(null);
@@ -38,6 +44,21 @@
     prevConnectionStatusRef.current = connectionStatus;
   }
 
+  const placeholder = React.useMemo(() => {
+    if (!encryptedThumbHash) {
+      return null;
+    }
+    try {
+      const decryptedThumbHash = decryptBase64(
+        encryptedThumbHash,
+        encryptionKey,
+      );
+      return { thumbhash: decryptedThumbHash };
+    } catch (e) {
+      return null;
+    }
+  }, [encryptedThumbHash, encryptionKey]);
+
   React.useEffect(() => {
     let isMounted = true;
     setSource(null);
@@ -74,6 +95,7 @@
 
   return (
     <LoadableImage
+      placeholder={placeholder}
       source={source}
       onLoad={onLoad}
       spinnerColor={spinnerColor}
diff --git a/native/media/loadable-image.react.js b/native/media/loadable-image.react.js
--- a/native/media/loadable-image.react.js
+++ b/native/media/loadable-image.react.js
@@ -8,6 +8,7 @@
 import type { ImageStyle } from '../types/styles.js';
 
 type Props = {
+  +placeholder: ?ImageSource,
   +source: ?ImageSource,
   +onLoad: () => void,
   +spinnerColor: string,
@@ -15,7 +16,7 @@
   +invisibleLoad: boolean,
 };
 function LoadableImage(props: Props): React.Node {
-  const { source, onLoad: onLoadProp } = props;
+  const { source, placeholder, onLoad: onLoadProp } = props;
 
   const [loaded, setLoaded] = React.useState(false);
 
@@ -30,7 +31,14 @@
   );
 
   if (!loaded && props.invisibleLoad) {
-    return <Image source={source} onLoad={onLoad} style={invisibleStyle} />;
+    return (
+      <Image
+        source={source}
+        placeholder={placeholder}
+        onLoad={onLoad}
+        style={invisibleStyle}
+      />
+    );
   }
 
   let spinner;
@@ -44,8 +52,13 @@
 
   return (
     <View style={styles.container}>
+      <Image
+        source={source}
+        placeholder={placeholder}
+        onLoad={onLoad}
+        style={props.style}
+      />
       {spinner}
-      <Image source={source} onLoad={onLoad} style={props.style} />
     </View>
   );
 }
diff --git a/native/media/multimedia.react.js b/native/media/multimedia.react.js
--- a/native/media/multimedia.react.js
+++ b/native/media/multimedia.react.js
@@ -16,11 +16,13 @@
   | {
       +kind: 'uri',
       +uri: string,
+      +thumbHash?: ?string,
     }
   | {
       +kind: 'encrypted',
       +holder: string,
       +encryptionKey: string,
+      +thumbHash?: ?string,
     };
 type BaseProps = {
   +mediaInfo: MediaInfo | AvatarMediaInfo,
@@ -111,6 +113,7 @@
     if (source.kind === 'encrypted') {
       return (
         <EncryptedImage
+          thumbHash={source.thumbHash}
           holder={source.holder}
           encryptionKey={source.encryptionKey}
           onLoad={this.onLoad}
@@ -121,12 +124,14 @@
         />
       );
     }
-    const { uri } = source;
+    const { uri, thumbHash } = source;
+    const placeholder = thumbHash ? { thumbhash: thumbHash } : null;
     if (uri.startsWith('http')) {
       return (
         <RemoteImage
           uri={uri}
           onLoad={this.onLoad}
+          placeholder={placeholder}
           spinnerColor={this.props.spinnerColor}
           style={styles.image}
           invisibleLoad={invisibleLoad}
@@ -138,6 +143,7 @@
         <Image
           source={{ uri }}
           onLoad={this.onLoad}
+          placeholder={placeholder}
           style={styles.image}
           key={uri}
         />
diff --git a/native/media/remote-image.react.js b/native/media/remote-image.react.js
--- a/native/media/remote-image.react.js
+++ b/native/media/remote-image.react.js
@@ -1,6 +1,7 @@
 // @flow
 
 import * as React from 'react';
+import type { ImageSource } from 'react-native/Libraries/Image/ImageSource';
 
 import { type ConnectionStatus } from 'lib/types/socket-types.js';
 
@@ -14,6 +15,7 @@
   +spinnerColor: string,
   +style: ImageStyle,
   +invisibleLoad: boolean,
+  +placeholder?: ?ImageSource,
 };
 type Props = {
   ...BaseProps,
@@ -41,12 +43,13 @@
   }
 
   render() {
-    const { style, spinnerColor, invisibleLoad, uri } = this.props;
+    const { style, spinnerColor, invisibleLoad, uri, placeholder } = this.props;
     const source = { uri };
 
     return (
       <LoadableImage
         source={source}
+        placeholder={placeholder}
         onLoad={this.onLoad}
         spinnerColor={spinnerColor}
         style={style}