-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: custom hook for offline/online status and fallback component (#…
…1142) Adds a custom hook for offline/online status and fallback component which can be rendered to display connectivity issues. Also adds example component which can be added to pages to test functionality. Will need to remove before merging.
- Loading branch information
Showing
5 changed files
with
265 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import {useIsOnline} from '../../../utils/customHooks'; | ||
import {Typography, Button, Box} from '@mui/material'; | ||
import RefreshIcon from '@mui/icons-material/Refresh'; | ||
|
||
interface ExampleOnlineComponentProps {} | ||
|
||
const ExampleOnlineComponent = (props: ExampleOnlineComponentProps) => { | ||
/** | ||
* Component: ExampleOnlineComponent | ||
* Debugging example component which can be used to test areas of the app | ||
* sensitive to being online. Utilizes the useIsOnline hook which provides a | ||
* fallback component. | ||
*/ | ||
const {isOnline, fallback, checkIsOnline} = useIsOnline(); | ||
|
||
if (!isOnline) { | ||
return fallback; | ||
} | ||
|
||
return ( | ||
<Box | ||
display="flex" | ||
flexDirection="column" | ||
alignItems="center" | ||
justifyContent="center" | ||
minHeight="100vh" | ||
> | ||
<Typography variant="h4" gutterBottom> | ||
You're online! | ||
</Typography> | ||
<Typography variant="body1" gutterBottom> | ||
This component is visible because you have an active internet | ||
connection. | ||
</Typography> | ||
<Button | ||
variant="contained" | ||
color="primary" | ||
startIcon={<RefreshIcon />} | ||
onClick={checkIsOnline} | ||
style={{marginTop: '16px'}} | ||
> | ||
Check Connection | ||
</Button> | ||
</Box> | ||
); | ||
}; | ||
|
||
export default ExampleOnlineComponent; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import HomeIcon from '@mui/icons-material/Home'; | ||
import RefreshIcon from '@mui/icons-material/Refresh'; | ||
import SignalWifiConnectedNoInternet4Icon from '@mui/icons-material/SignalWifiConnectedNoInternet4'; | ||
import {Box, Button, CircularProgress, Typography} from '@mui/material'; | ||
import {useState} from 'react'; | ||
import {createUseStyles as makeStyles} from 'react-jss'; | ||
import {theme} from '../../themes'; | ||
|
||
const useStyles = makeStyles({ | ||
root: { | ||
display: 'flex', | ||
flexDirection: 'column', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
minHeight: '100vh', | ||
backgroundColor: theme.palette.background.default, | ||
}, | ||
container: { | ||
padding: theme.spacing(4), | ||
backgroundColor: theme.palette.background.paper, | ||
borderRadius: theme.shape.borderRadius, | ||
boxShadow: theme.shadows[1], | ||
textAlign: 'center', | ||
}, | ||
logo: { | ||
width: theme.spacing(12), | ||
height: theme.spacing(12), | ||
marginBottom: theme.spacing(3), | ||
color: theme.palette.text.secondary, | ||
}, | ||
title: { | ||
color: theme.palette.text.primary, | ||
marginBottom: theme.spacing(1), | ||
}, | ||
subtitle: { | ||
color: theme.palette.text.secondary, | ||
marginBottom: theme.spacing(3), | ||
}, | ||
buttonContainer: { | ||
'& > :not(:last-child)': { | ||
marginRight: theme.spacing(2), | ||
}, | ||
}, | ||
primaryButton: { | ||
backgroundColor: theme.palette.primary.main, | ||
color: theme.palette.primary.contrastText, | ||
'&:hover': { | ||
backgroundColor: theme.palette.primary.dark, | ||
}, | ||
}, | ||
secondaryButton: { | ||
color: theme.palette.text.primary, | ||
borderColor: theme.palette.text.primary, | ||
'&:hover': { | ||
backgroundColor: theme.palette.action.hover, | ||
}, | ||
}, | ||
buttonProgress: { | ||
color: theme.palette.primary.contrastText, | ||
position: 'absolute', | ||
top: '50%', | ||
left: '50%', | ||
marginTop: -12, | ||
marginLeft: -12, | ||
}, | ||
}); | ||
|
||
export interface OfflineFallbackComponentProps { | ||
/** What should happen when refresh button is pressed? */ | ||
onRefresh: () => void; | ||
/** What should happen when return home button is pressed? */ | ||
onReturnHome: () => void; | ||
} | ||
|
||
/** | ||
* A fallback component to use when part of app is offline. | ||
* @param props controls for buttons in the fallback | ||
* @returns A splashscreen which lays over the given view with an offline logo, | ||
* refresh and return home button. | ||
*/ | ||
export const OfflineFallbackComponent = ( | ||
props: OfflineFallbackComponentProps | ||
) => { | ||
const classes = useStyles(); | ||
const {onRefresh, onReturnHome} = props; | ||
// A fake loading state to reassure user that something happened. | ||
const [loading, setLoading] = useState(false); | ||
|
||
// Just show loading for 500ms when button is pressed | ||
const handleRefresh = () => { | ||
setLoading(true); | ||
setTimeout(() => { | ||
setLoading(false); | ||
onRefresh(); | ||
}, 500); | ||
}; | ||
|
||
return ( | ||
<Box className={classes.root}> | ||
<Box className={classes.container}> | ||
<SignalWifiConnectedNoInternet4Icon className={classes.logo} /> | ||
<Typography variant="h4" component="h1" className={classes.title}> | ||
Uh oh... you're offline. | ||
</Typography> | ||
<Typography variant="subtitle1" className={classes.subtitle}> | ||
This part of the app doesn't work offline. | ||
</Typography> | ||
<Box className={classes.buttonContainer}> | ||
<Button | ||
variant="contained" | ||
className={classes.primaryButton} | ||
startIcon={loading ? null : <RefreshIcon />} | ||
onClick={handleRefresh} | ||
disabled={loading} | ||
> | ||
{loading ? 'Refreshing...' : 'Refresh'} | ||
{loading && ( | ||
<CircularProgress size={24} className={classes.buttonProgress} /> | ||
)} | ||
</Button> | ||
<Button | ||
variant="outlined" | ||
className={classes.secondaryButton} | ||
startIcon={<HomeIcon />} | ||
onClick={onReturnHome} | ||
> | ||
Return Home | ||
</Button> | ||
</Box> | ||
</Box> | ||
</Box> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import React, {useEffect, useRef, useState} from 'react'; | ||
import {useNavigate} from 'react-router'; | ||
import * as ROUTES from '../constants/routes'; | ||
import {OfflineFallbackComponent} from '../gui/components/ui/OfflineFallback'; | ||
|
||
export const usePrevious = <T extends {}>(value: T): T | undefined => { | ||
/** | ||
* Capture the previous value of a state variable (useful for functional components | ||
* in place of class-based lifecycle method componentWillUpdate) | ||
*/ | ||
const ref = useRef<T>(); | ||
useEffect(() => { | ||
ref.current = value; | ||
}); | ||
return ref.current; | ||
}; | ||
|
||
export interface UseIsOnlineResponse { | ||
// does the browser have network connectivity? | ||
isOnline: boolean; | ||
// forcefully recheck online status | ||
checkIsOnline: () => boolean; | ||
// fallback component which can be used when the browser is offline to hide | ||
// components | ||
fallback: React.ReactNode; | ||
} | ||
|
||
/** | ||
* Custom hook to provide a remount for offline/online status change. Should be | ||
* used in situations where the app only works online. Provides a fallback | ||
* component. | ||
* @returns offline/online status as well as a fallback component to render over | ||
* the page if offline | ||
*/ | ||
export function useIsOnline(): UseIsOnlineResponse { | ||
// online/offline state | ||
const [online, setOnline] = useState(window.navigator.onLine); | ||
// Routing | ||
const navigate = useNavigate(); | ||
|
||
// Create handlers for window online/offline events and clean up from use effect | ||
useEffect(() => { | ||
function handleOnline() { | ||
setOnline(true); | ||
} | ||
function handleOffline() { | ||
setOnline(false); | ||
} | ||
window.addEventListener('online', handleOnline); | ||
window.addEventListener('offline', handleOffline); | ||
return () => { | ||
window.removeEventListener('online', handleOnline); | ||
window.removeEventListener('offline', handleOffline); | ||
}; | ||
}, []); | ||
|
||
/** | ||
* Forcefully checks is online and uses setOnline to prompt remount where | ||
* necessary. Should be redundant given event handlers but might be useful for | ||
* critical manual checkpoints. | ||
* @returns Current online status | ||
*/ | ||
const checkIsOnline = () => { | ||
const online = window.navigator.onLine; | ||
setOnline(online); | ||
return online; | ||
}; | ||
|
||
return { | ||
isOnline: online, | ||
checkIsOnline: checkIsOnline, | ||
fallback: ( | ||
<OfflineFallbackComponent | ||
onRefresh={() => { | ||
checkIsOnline(); | ||
}} | ||
onReturnHome={() => { | ||
navigate(ROUTES.INDEX); | ||
}} | ||
></OfflineFallbackComponent> | ||
), | ||
}; | ||
} |
This file was deleted.
Oops, something went wrong.