prevent state updates after component unmounted
This commit is contained in:
parent
c8f21025d1
commit
036cf734c6
5 changed files with 110 additions and 72 deletions
|
@ -1215,4 +1215,4 @@
|
|||
"%duration% minute ago": "%duration% minute ago",
|
||||
"%duration% seconds ago": "%duration% seconds ago",
|
||||
"%duration% second ago": "%duration% second ago"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<div className="file-render__viewer file-render__viewer--comic">
|
||||
{ loading && <LoadingScreen status={__('Loading')} />}
|
||||
{ ready && <Villain source={finalSource.content} className={'comic-viewer'} options={opts} workerUrl={workerUrl} /> }
|
||||
{ error && <LoadingScreen status={errorMessage} spinner={false} /> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="file-render__viewer file-render__viewer--comic">
|
||||
{loading && <LoadingScreen status={__('Loading')} />}
|
||||
{ready && (
|
||||
<Villain source={finalSource.content} className={'comic-viewer'} options={opts} workerUrl={workerUrl} />
|
||||
)}
|
||||
{error && <LoadingScreen status={errorMessage} spinner={false} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComicBookViewer;
|
||||
|
|
17
ui/effects/use-is-mounted.js
Normal file
17
ui/effects/use-is-mounted.js
Normal file
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue