diff --git a/static/app-strings.json b/static/app-strings.json
index b0ce17374..f35901ba2 100644
--- a/static/app-strings.json
+++ b/static/app-strings.json
@@ -1215,4 +1215,4 @@
"%duration% minute ago": "%duration% minute ago",
"%duration% seconds ago": "%duration% seconds ago",
"%duration% second ago": "%duration% second ago"
-}
\ No newline at end of file
+}
diff --git a/ui/component/viewers/comicBookViewer.jsx b/ui/component/viewers/comicBookViewer.jsx
index fbe21e05e..05ca41c87 100644
--- a/ui/component/viewers/comicBookViewer.jsx
+++ b/ui/component/viewers/comicBookViewer.jsx
@@ -4,11 +4,11 @@ import Villain from 'villain-react';
import LoadingScreen from 'component/common/loading-screen';
// @if TARGET='web'
-import useStream from 'effects/use-stream'
+import useStream from 'effects/use-stream';
// @endif
// @if TARGET='app'
-import useFileStream from 'effects/use-stream-file'
+import useFileStream from 'effects/use-stream-file';
// @endif
// Import default styles for Villain
@@ -29,39 +29,39 @@ if (process.env.NODE_ENV !== 'production') {
workerUrl = `/${workerUrl}`;
}
-const ComicBookViewer = ( props: Props) => {
- const { source, theme } = props
- const { stream, file } = source
+const ComicBookViewer = (props: Props) => {
+ const { source, theme } = props;
+ let finalSource;
- // @if TARGET='web'
- const finalSource = useStream(stream)
- // @endif
+ // @if TARGET='web'
+ finalSource = useStream(source.stream);
+ // @endif
- // @if TARGET='app'
- const finalSource = useFileStream(file)
- // @endif
+ // @if TARGET='app'
+ finalSource = useFileStream(source.file);
+ // @endif
- const { error, loading, content } = finalSource
-
- const ready = content !== null && !loading
+ // Villain options
+ const opts = {
+ theme: theme === 'dark' ? 'Dark' : 'Light',
+ allowFullScreen: true,
+ autoHideControls: false,
+ allowGlobalShortcuts: true,
+ };
- // Villain options
- const opts = {
- theme: theme === 'dark' ? 'Dark' : 'Light',
- allowFullScreen: true,
- autoHideControls: false,
- allowGlobalShortcuts: true,
- };
+ const { error, loading, content } = finalSource;
+ const ready = content !== null && !loading;
+ const errorMessage = __("Sorry, looks like we can't load the archive.");
- const errorMessage = __("Sorry, looks like we can't load the archive.");
-
- return (
-
- { loading && }
- { ready && }
- { error && }
-
- );
- }
+ return (
+
+ {loading && }
+ {ready && (
+
+ )}
+ {error && }
+
+ );
+};
export default ComicBookViewer;
diff --git a/ui/effects/use-is-mounted.js b/ui/effects/use-is-mounted.js
new file mode 100644
index 000000000..0337e2b36
--- /dev/null
+++ b/ui/effects/use-is-mounted.js
@@ -0,0 +1,17 @@
+import React from 'react';
+
+// Check if component is mounted, useful to prevent state updates after component unmounted
+function useIsMounted() {
+ const isMounted = React.useRef(true);
+
+ React.useEffect(() => {
+ return () => {
+ isMounted.current = false;
+ };
+ }, []);
+
+ // Returning "isMounted.current" wouldn't work because we would return unmutable primitive
+ return isMounted;
+}
+
+export default useIsMounted;
diff --git a/ui/effects/use-stream-file.js b/ui/effects/use-stream-file.js
index 0c18fbc27..f86c8fa56 100644
--- a/ui/effects/use-stream-file.js
+++ b/ui/effects/use-stream-file.js
@@ -1,36 +1,46 @@
-// @flow
import React from 'react';
+import useIsMounted from 'effects/use-is-mounted';
// Returns a blob from the download path
-export default function useFileStream(fileStream: (?string) => any) {
+export default function useFileStream(fileStream) {
+ const isMounted = useIsMounted();
const [state, setState] = React.useState({
error: false,
- content: null,
loading: true,
+ content: null,
});
React.useEffect(() => {
- if (fileStream) {
- let chunks = []
+ if (fileStream && isMounted.current) {
+ let chunks = [];
const stream = fileStream();
-
+ // Handle steam chunk recived
stream.on('data', chunk => {
- chunks.push(chunk)
+ if (isMounted.current) {
+ chunks.push(chunk);
+ } else {
+ // Cancel stream if component is not mounted:
+ // The user has left the viewer page
+ stream.destroy();
+ }
});
-
+ // Handle stream ended
stream.on('end', () => {
- const buffer = Buffer.concat(chunks)
- const blob = new Blob([buffer])
- setState({ content: blob, loading: false });
+ if (isMounted.current) {
+ const buffer = Buffer.concat(chunks);
+ const blob = new Blob([buffer]);
+ setState({ content: blob, loading: false });
+ }
});
-
+ // Handle stream error
stream.on('error', () => {
- setState({ error: true, loading: false });
-
+ if (isMounted.current) {
+ setState({ error: true, loading: false });
+ }
});
}
- }, []);
+ }, [fileStream, isMounted]);
return state;
}
diff --git a/ui/effects/use-stream.js b/ui/effects/use-stream.js
index d317a2aab..c32fdcaf3 100644
--- a/ui/effects/use-stream.js
+++ b/ui/effects/use-stream.js
@@ -1,45 +1,56 @@
-// @flow
import React from 'react';
import https from 'https';
+import useIsMounted from 'effects/use-is-mounted';
// Returns web blob from the streaming url
-export default function useStream(url: (?string) => any) {
+export default function useStream(url) {
+ const isMounted = useIsMounted();
const [state, setState] = React.useState({
error: false,
+ loading: true,
content: null,
- loading: false,
});
React.useEffect(() => {
- if (url) {
- let chunks = [];
-
- // Start loading state
- setState({loading: true})
-
- https.get(
- url,
- response => {
- if (response.statusCode >= 200 && response.statusCode < 300) {
- let chunks = []
- response.on('data', function(chunk) {
+ if (url && isMounted.current) {
+ https.get(url, response => {
+ if (isMounted && response.statusCode >= 200 && response.statusCode < 300) {
+ let chunks = [];
+ // Handle stream chunk recived
+ response.on('data', function(chunk) {
+ if (isMounted.current) {
chunks.push(chunk);
- });
- response.on('end', () => {
- const buffer = Buffer.concat(chunks)
- const blob = new Blob([buffer])
- console.info(response)
+ } else {
+ // Cancel stream if component is not mounted:
+ // The user has left the viewer page
+ response.destroy();
+ }
+ });
+ // Handle stream ended
+ response.on('end', () => {
+ if (isMounted.current) {
+ const buffer = Buffer.concat(chunks);
+ const blob = new Blob([buffer]);
+ console.info(response);
setState({ content: blob, loading: false });
- });
- } else {
- console.info(response)
+ }
+ });
+ // Handle stream error
+ response.on('error', () => {
+ if (isMounted.current) {
+ setState({ error: true, loading: false });
+ }
+ });
+ } else {
+ // Handle network error
+ if (isMounted.current) {
setState({ error: true, loading: false });
}
}
- );
+ });
}
- }, []);
+ }, [url, isMounted]);
return state;
}