Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I cancel an Image.getSize() request in React Native?

I'm using React Native's Image.getSize(uri, (width, height) => {}) method to get the dimensions of a remote image and set it to a component's state with setState():

componentDidMount() {
  Image.getSize(this.props.uri, (width, height) => {
    this.setState({ width, height })
  })
}

However, sometimes the component unmounts before the getSize() request has returned, and this leads to the following error when setState() is called:

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

I understand that it's possible to keep track of a component's mounted state to prevent the setState() call, but apparently this is considered an antipattern.

Is there any other way that this can be achieved, given that getSize() does not provide a way to cancel a pending request?

like image 249
pjivers Avatar asked Jan 20 '20 04:01

pjivers


1 Answers

I encountered the same problem. What worked for me was wrapping Image.getSize inside a promise:

export type CancelPromise = ((reason?: Error) => void) | undefined;
export type ImageSize = { width: number; height: number };
interface ImageSizeOperation {
  start: () => Promise<ImageSize>;
  cancel: CancelPromise;
}

const getImageSize = (uri: string): ImageSizeOperation => {
  let cancel: CancelPromise;
  const start = (): Promise<ImageSize> =>
    new Promise<{ width: number; height: number }>((resolve, reject) => {
      cancel = reject;
      Image.getSize(
        uri,
        (width, height) => {
          cancel = undefined;
          resolve({ width, height });
        },
        error => {
          reject(error);
        }
      );
    });

  return { start, cancel };
};

Then you could use it in your component like this:

const A: React.FC = () => {
  const [size, setSize] = useState<{width: number, height: number} | undefined>(});
  useEffect(() => {
    let cancel: CancelPromise;
    const sideEffect = async (): Promise<void> => {
      try {
        const operation = getImageSize(uri);
        cancel = operation.cancel;
        const imageSize = await operation.start();
        setSize(imageSize);
      } catch(error) {
        if (__DEV__) console.warn(error);
      }
    }
    sideEffect();
    return () => { 
      if (cancel) {
        cancel(); 
      }
    }
  });
}
like image 183
dpnolte Avatar answered Oct 24 '22 18:10

dpnolte