<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anoop Mundathan</title>
    <description>The latest articles on DEV Community by Anoop Mundathan (@anoopmundathan).</description>
    <link>https://dev.to/anoopmundathan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F462993%2F0ed788c9-e0b7-4697-a34b-213e64ad32fd.jpeg</url>
      <title>DEV Community: Anoop Mundathan</title>
      <link>https://dev.to/anoopmundathan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anoopmundathan"/>
    <language>en</language>
    <item>
      <title>How to retry image when error occurs in React?</title>
      <dc:creator>Anoop Mundathan</dc:creator>
      <pubDate>Fri, 21 Jan 2022 12:22:41 +0000</pubDate>
      <link>https://dev.to/anoopmundathan/how-to-retry-image-in-react-36nh</link>
      <guid>https://dev.to/anoopmundathan/how-to-retry-image-in-react-36nh</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;I am trying to load an image and in my case image is hosted in AWS S3 bucket, for some strange reason, image is not available immediately for client to consume and was experiencing a broken thumbnail image in frontend.&lt;/p&gt;

&lt;p&gt;Image is getting ready after couple of seconds. So in this case, I wanted to retry image url again until it’s available. &lt;/p&gt;

&lt;p&gt;I am using onerror event handler on this purpose, which gets called when error occurs while loading or rendering an image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1:
&lt;/h3&gt;

&lt;p&gt;First I wanted to keep track number of retries. For this I created a useRef variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const componentRef = useRef&amp;lt;number&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2:
&lt;/h3&gt;

&lt;p&gt;Initialise ref variable in first render. This is done in useEffect hook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(() =&amp;gt; {
    componentRef.current = RETRY_COUNT;
}, []);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3:
&lt;/h3&gt;

&lt;p&gt;Add onError handler in img tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Image src={s3Url} alt={filename} onError={handleError} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4:
&lt;/h3&gt;

&lt;p&gt;Set error state, Set image source and decrease retry count.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; const handleError = useCallback(({ currentTarget }) =&amp;gt; {

    setError(true);
    if (componentRef &amp;amp;&amp;amp; componentRef.current &amp;amp;&amp;amp; componentRef.current &amp;gt; 0) {
      setTimeout(() =&amp;gt; {
        currentTarget.onerror = null;
        currentTarget.src = s3Url;
        componentRef.current =
          componentRef &amp;amp;&amp;amp; componentRef.current &amp;amp;&amp;amp; componentRef.current - 1;
      }, RETRY_DELAY);
    }
  }, []);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have wrapped this logic in setTimeout with 1 second delay, so that this retry will happen in each second.&lt;/p&gt;

&lt;p&gt;As a final step to improve UX, you may show loading indicator  while image is being fetched. For that you could create a state variable as below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [error, setError] = useState&amp;lt;boolean&amp;gt;(false);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set error state&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handleError = useCallback(({ currentTarget }) =&amp;gt; {
    setError(true);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add onload handler in image tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Image onLoad={handleLoad} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set error state false.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handleLoad = useCallback(() =&amp;gt; {
    setError(false);
  }, []);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Show loading indicator&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (error) {
     return &amp;lt;Loading /&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  And finally, here is the full source code.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState
} from "react";
import styled from "styled-components";
import { Loading } from "./loading";

const RETRY_COUNT = 5;
const RETRY_DELAY = 1000;

const Image = styled.img`
  width: 100%;
  height: 100%;
  border-radius: 6px;
`;

interface ImagePreviewProps {
  filename: string;
  s3Url: string;
}

export const ImagePreview: React.FC&amp;lt;ImagePreviewProps&amp;gt; = ({
  filename,
  s3Url,
  loading
}) =&amp;gt; {

  const componentRef = useRef&amp;lt;number&amp;gt;();
  const [error, setError] = useState&amp;lt;boolean&amp;gt;(false);

  useEffect(() =&amp;gt; {
    componentRef.current = RETRY_COUNT;
  }, []);

  const handleError = useCallback(({ currentTarget }) =&amp;gt; {
    setError(true);
    if (componentRef &amp;amp;&amp;amp; componentRef.current &amp;amp;&amp;amp; componentRef.current &amp;gt; 0) {
      setTimeout(() =&amp;gt; {
        currentTarget.onerror = null;
        currentTarget.src = s3Url;
        componentRef.current =
          componentRef &amp;amp;&amp;amp; componentRef.current &amp;amp;&amp;amp; componentRef.current - 1;
      }, RETRY_DELAY);
    }
  }, []);

  const handleLoad = useCallback(() =&amp;gt; {
    setError(false);
  }, []);

  const showImage = useMemo(() =&amp;gt; {
    return (
          &amp;lt;Image
            src={s3Url}
            alt={filename}
            onError={handleError}
            onLoad={handleLoad}
          /&amp;gt;
        )
    );
  }, [loading, filename, s3Url]);

  if (error) {
     return &amp;lt;Loading /&amp;gt;
  }

  return showImage;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>react</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>image</category>
    </item>
  </channel>
</rss>
