Skip to content

Commit b597da1

Browse files
committed
redux store separation from root layout
1 parent 5e7ce5c commit b597da1

File tree

4 files changed

+40
-33
lines changed

4 files changed

+40
-33
lines changed

src/app/[locale]/[...slug]/page.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@
22

33
// This page is temporary to ease the migration to Next.js App Router
44
// It will be deprecated once the migration is fully complete
5-
import { type ReactNode, use } from 'react';
5+
import { type ReactNode, use, useEffect } from 'react';
66
import dynamic from 'next/dynamic';
7+
import { PersistGate } from 'redux-persist/integration/react';
8+
import { persistStore } from 'redux-persist';
9+
import { store } from '../../store/store';
10+
import { useAppDispatch } from '../../hooks';
11+
import { resetProfileErrors } from '../../store/profile-reducer';
712

813
const App = dynamic(async () => await import('../../App'), { ssr: false });
914

15+
const persistor = persistStore(store);
16+
1017
interface PageProps {
1118
params: Promise<{
1219
locale: string;
@@ -16,7 +23,18 @@ interface PageProps {
1623

1724
export default function Page({ params }: PageProps): ReactNode {
1825
const { locale } = use(params);
26+
const pathKey = use(params).slug?.join('/') ?? '/';
27+
const dispatch = useAppDispatch();
28+
29+
useEffect(() => {
30+
// Clean errors from previous session
31+
dispatch(resetProfileErrors());
32+
}, [dispatch]);
1933

2034
// Pass locale to App so BrowserRouter can use correct basename
21-
return <App locale={locale} />;
35+
return (
36+
<PersistGate loading={null} persistor={persistor}>
37+
<App locale={locale} key={pathKey} />;
38+
</PersistGate>
39+
);
2240
}

src/app/components/Context.tsx

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,20 @@
11
'use client';
22

3-
import React, { useEffect } from 'react';
3+
import React from 'react';
44
import type ContextProviderProps from '../interface/ContextProviderProps';
55
import { Provider } from 'react-redux';
66
import { store } from '../store/store';
7-
import { PersistGate } from 'redux-persist/integration/react';
8-
import { persistStore } from 'redux-persist';
9-
import { useAppDispatch } from '../hooks';
10-
import { resetProfileErrors } from '../store/profile-reducer';
117

12-
const persistor = persistStore(store);
138
/**
14-
* This component is used to wrap the entire application
15-
*/
16-
const AppContent: React.FC<ContextProviderProps> = ({ children }) => {
17-
const dispatch = useAppDispatch();
18-
19-
useEffect(() => {
20-
// This function will run when the component is first loaded
21-
// Clean errros from previous session
22-
dispatch(resetProfileErrors());
23-
}, []);
24-
return (
25-
<PersistGate loading={children} persistor={persistor}>
26-
{children}
27-
</PersistGate>
28-
);
29-
};
30-
31-
/**
32-
* This component is used to wrap the entire application adding the store provider and reseting the errors from previous session
9+
* This component is used to wrap the entire application with Redux Provider
10+
* It provides the Redux store to all components in the app
11+
* IMPORTANT: This does not include the PersistGate, which is used in the page component to delay rendering until the store is rehydrated
12+
* This allows for a fast initial render, but makes it possible for components to access the store before it's fully rehydrated.
13+
* This also allows for us to have SSG pages that use Redux. It also allows for these pages to be rendered purely in HTML on the server side without waiting for Redux Persist to rehydrate the store.
14+
* Use the `useRehydrated` hook to check rehydration status if needed.
3315
*/
3416
const ContextProviders: React.FC<ContextProviderProps> = ({ children }) => {
35-
return (
36-
<Provider store={store}>
37-
<AppContent>{children}</AppContent>
38-
</Provider>
39-
);
17+
return <Provider store={store}>{children}</Provider>;
4018
};
4119

4220
export default ContextProviders;

src/app/components/LogoutConfirmModal.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
Dialog,
1111
} from '@mui/material';
1212
import React from 'react';
13-
import { useAppDispatch } from '../hooks';
13+
import { useAppDispatch, useRehydrated } from '../hooks';
1414
import { logout } from '../store/profile-reducer';
1515
import { SIGN_OUT_TARGET } from '../constants/Navigation';
1616
import { useRouter } from 'next/navigation';
@@ -26,6 +26,7 @@ export default function ConfirmModal({
2626
}: ConfirmModalProps): React.ReactElement {
2727
const dispatch = useAppDispatch();
2828
const router = useRouter();
29+
const isRehydrated = useRehydrated();
2930
const confirmLogout = (): void => {
3031
dispatch(
3132
logout({
@@ -65,6 +66,7 @@ export default function ConfirmModal({
6566
color='primary'
6667
variant='contained'
6768
data-cy='confirmSignOutButton'
69+
disabled={!isRehydrated}
6870
>
6971
Confirm
7072
</Button>

src/app/hooks/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,12 @@ import { type RootState, type AppDispatch } from '../store/store';
88
// Use throughout your app instead of plain `useDispatch` and `useSelector`
99
export const useAppDispatch = (): AppDispatch => useDispatch<AppDispatch>();
1010
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
11+
12+
// Hook to check if redux-persist has finished rehydrating the store
13+
// This allows us to display content before the store is fully rehydrated while giving us a way to check rehydration status if needed (e.g. to delay rendering of certain components until rehydration is complete)
14+
export const useRehydrated = (): boolean => {
15+
return useAppSelector((state) => {
16+
// Redux-persist adds a _persist key to the state with rehydrated status
17+
return state?._persist?.rehydrated ?? false;
18+
});
19+
};

0 commit comments

Comments
 (0)