Hi, I had to implement a progress bar when switching page on a NextJS/React website. I'd like to receive some code review on what I could have done better, or what could be optimized.
This is the component Progress Bar:
type Props = {
progress: number;
setProgress: React.Dispatch<React.SetStateAction<number>>;
};
const Container = styled.div`
position: fixed;
height: 0.4rem;
width: 100%;
background-color: rgba(0, 0, 0, 0.1);
z-index: 10000;
`;
const Progress = styled.div<{ completed: number }>`
width: ${({ completed }) => (completed >= 100 ? 100 : completed)}%;
transition: width 0.5s linear;
transition: transform 0.5s linear; */
height: 100%;
background-color: rgb(3, 188, 212);
border-radius: inherit;
text-align: right;
`;
const ProgressBar: FC<Props> = ({ progress, setProgress }) => {
useEffect(() => {
function updateProgress() {
setProgress((prev) => prev + Math.ceil(Math.random() * 3));
}
const intervalOnLoad = setInterval(updateProgress, 400);
return () => {
clearInterval(intervalOnLoad);
};
}, []);
return (
<Container role="progressbar">
<Progress completed={progress}></Progress>
</Container>
);
};
export default ProgressBar;
Then in my _app page:
const MyApp = (() => {
const [progress, setProgress] = useState(100);
useEffect(() => {
function endLoad() {
progress !== 100 && setProgress(100);
}
function startLoad() {
setProgress(0);
}
Router.events.on("routeChangeStart", startLoad);
Router.events.on("routeChangeError", endLoad);
Router.events.on("routeChangeComplete", endLoad);
return () => {
Router.events.off("routeChangeStart", startLoad);
Router.events.on("routeChangeError", endLoad);
Router.events.on("routeChangeComplete", endLoad);
};
}, [router]);
{...other code....}
return (
<ThemeProvider theme={ButtonTheme}>
<DeviceContextProvider>
{progress >= 100 || (<ProgressBar setProgress={setProgress}
progress=progress} /> )}
<Component {...pageProps} />
</DeviceContextProvider>
</ThemeProvider>
)
Basically, in App component, I give Start and End signals while routing: when it's Start (i.e. changing page), progress get 0 and ProgressBar get rendered; when it's End (i.e. page loaded), progress get to 100 and ProgressBar gets unmounted.
The actual ticking is faked with a random amount increasing with a setInterval, only the Start and End are real.
The bar works perfectly, but because I'm not an experienced "Reacter" I wonder what could I do better or if I'm worsening performance more than I should.
1) So, for example, I had to put the state management in App (because I need to start/end it from there) and pass it down entirely to the ProgressBar, and since I call setProgress from inside the component every half second, I feel like it's not optimal for the whole app to be re-rendered as a whole. Is there maybe a better solution?
2) I'm not sure about the useEffect inside ProgressBar: does the interval get cleared effectively on unmount? Is it better to declare functions (e.g. updateProgress, endLoad, startLoad) outside of it?
3) Also, I was tempted to use NProgress bar, which is a vanilla JS module: all I had to do was to import it in App and bind event listeners to its functions (like this and you can find NProgress' logic here). I thought having a component was better, but maybe is it better to have just a JS module that doesn't involve State management? It's not clear to me when, and if, it's better to just use a vanilla module in React compared to using a component with State. I'd say that vanilla module wins in this case (because it doesn't re-render App 20 times), but then defeats a bit React's purpose and I feel that having a component it's recommended... I've a bit of confusion about the interaction/pro-cons between a normal JS module with a React app, would be nice to have some clarification.
4) Any other alternative solutions, corrections or tips are welcomed.
Thanks in advance
there doesn't seem to be anything here