Skip to content

Commit

Permalink
Proper gipp fix (#512)
Browse files Browse the repository at this point in the history
  • Loading branch information
voinik authored Dec 15, 2022
1 parent 4ee5069 commit 983e750
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 11 deletions.
2 changes: 2 additions & 0 deletions packages/demo-redux-toolkit/pages/detail/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const Page: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = (
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/detail/2">Go to details id=2</Link>
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/gipp">Go to gip page</Link>
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/pokemon/pikachu">Go to Pokemon</Link>
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/">Go to homepage</Link>
Expand Down
47 changes: 47 additions & 0 deletions packages/demo-redux-toolkit/pages/gipp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import {useDispatch, useSelector, useStore} from 'react-redux';
import Link from 'next/link';
import {NextPage} from 'next';
import {fetchGipp, selectGippPageData, selectGippPageStateTimestamp, selectGippPageTestData, wrapper} from '../store';

const Page: NextPage = () => {
console.log('State on render', useStore().getState());
const dispatch = useDispatch();
const testData = useSelector(selectGippPageTestData);
const stateTimestamp = useSelector(selectGippPageStateTimestamp);
const data = useSelector(selectGippPageData);

console[testData ? 'info' : 'warn']('Rendered testData: ', testData);

if (!testData || !data) {
throw new Error('Whoops! We do not have the data and testData selector data!');
}

return (
<>
<div style={{backgroundColor: 'lavender', padding: '20px'}}>Timestamp in state: {stateTimestamp}</div>
<div className={`page${1}`}>
<h3>{testData}</h3>
<Link href="/subject/1">Go id=1</Link>
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/subject/2">Go id=2</Link>
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/detail/1">Go to details id=1</Link>
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/detail/2">Go to details id=2</Link>
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/pokemon/pikachu">Go to Pokemon</Link>
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/">Go to homepage</Link>
</div>
<button onClick={() => dispatch(fetchGipp())}>Refresh timestamp</button>
</>
);
};

Page.getInitialProps = wrapper.getInitialPageProps(store => async () => {
await store.dispatch(fetchGipp());
return {};
});

export default Page;
4 changes: 4 additions & 0 deletions packages/demo-redux-toolkit/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export default function IndexPage() {
Go to detail pages
</Link>
<br />
<Link href="/gipp" prefetch={false}>
Go to gipp page
</Link>
<br />
<Link href="/pokemon/pikachu" prefetch={false}>
Go to Pokemon
</Link>
Expand Down
2 changes: 2 additions & 0 deletions packages/demo-redux-toolkit/pages/subject/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const Page: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = (
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/detail/2">Go to details id=2</Link>
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/gipp">Go to gip page</Link>
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/pokemon/pikachu">Go to Pokemon</Link>
&nbsp;&nbsp;&nbsp;&nbsp;
<Link href="/">Go to homepage</Link>
Expand Down
72 changes: 72 additions & 0 deletions packages/demo-redux-toolkit/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,46 @@ const detailPageSlice = createSlice({
},
});

// Gipp page model
interface GippPageData {
id: string | null;
testData: string | null;
stateTimestamp: number | null;
}

interface GippPageState {
data: GippPageData | null;
}

const gippPageInitialState: GippPageState = {
data: {
id: null,
testData: null,
stateTimestamp: null,
},
};

// Gipp page slice approach
const gippPageSlice = createSlice({
name: 'gippPage',
initialState: gippPageInitialState,
reducers: {
gippPageLoaded(state, {payload}: PayloadAction<GippPageState>) {
state.data = payload.data;
},
},
extraReducers: {
[HYDRATE]: (state, action) => {
console.log('HYDRATE gippPage', action.payload);

return {
...state,
...action.payload.gippPage,
};
},
},
});

interface Pokemon {
name: string;
}
Expand All @@ -108,6 +148,7 @@ export const {useGetPokemonByNameQuery} = pokemonApi;
const reducers = {
[subjectPageSlice.name]: subjectPageSlice.reducer,
[detailPageSlice.name]: detailPageSlice.reducer,
[gippPageSlice.name]: gippPageSlice.reducer,
[pokemonApi.reducerPath]: pokemonApi.reducer,
};

Expand Down Expand Up @@ -164,6 +205,23 @@ export const fetchDetail =
);
};

// Gipp page thunk
export const fetchGipp = (): AppThunk => async dispatch => {
const timeoutPromise = (timeout: number) => new Promise(resolve => setTimeout(resolve, timeout));

await timeoutPromise(200);

dispatch(
gippPageSlice.actions.gippPageLoaded({
data: {
id: 'gippId',
testData: 'This is the test data for the gipp page',
stateTimestamp: new Date().getTime(),
},
}),
);
};

// Subject page selectors
const subjectPageSliceSelector = (state: AppState): SubjectPageState => state.subjectPage;

Expand Down Expand Up @@ -195,3 +253,17 @@ export const selectDetailPageStateTimestamp = createSelector(selectDetailPageDat
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export const selectDetailPageSummary = createSelector(selectDetailPageData, s => s.summary);

// Gipp page selectors
const gippPageSliceSelector = (state: AppState): GippPageState => state.gippPage;

export const selectGippPageData = createSelector(gippPageSliceSelector, s => s.data);

// The correct way with strict typing on
export const selectGippPageId = createSelector(selectGippPageData, s => s?.id);
export const selectGippPageStateTimestamp = createSelector(selectGippPageData, s => s?.stateTimestamp);

// The incorrect way with strict typing off. See comment above.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export const selectGippPageTestData = createSelector(selectGippPageData, s => s?.testData);
32 changes: 21 additions & 11 deletions packages/wrapper/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,21 +161,22 @@ export const createWrapper = <S extends Store>(makeStore: MakeStore<S>, config:
} as any);
};

const hydrateOrchestrator = (store: S, gipState: any, gspState: any, gsspState: any) => {
const hydrateOrchestrator = (store: S, giapState: any, gspState: any, gsspState: any, gippState: any) => {
if (gspState) {
// If GSP has run, then gspState will _not_ contain the data from GIP (if it exists), because GSP is run at build time,
// and GIP runs at request time. So we have to hydrate the GIP data first, and then do another hydrate on the gspState.
hydrate(store, gipState);
hydrate(store, giapState);
hydrate(store, gspState);
} else if (gsspState || gipState) {
} else if (gsspState || gippState || giapState) {
// If GSSP has run, then gsspState _will_ contain the data from GIP (if there is a GIP) and the GSSP data combined
// (see https:/kirill-konshin/next-redux-wrapper/pull/499#discussion_r1014500941).
// If there is no GSP or GSSP for this page, but there is a GIP, then we use the gipState.
hydrate(store, gsspState ?? gipState);
// If there is no GSP or GSSP for this page, but there is a GIP on page level (not _app), then we use the gippState.
// If there is no GSP or GSSP and no GIP on page level for this page, but there is a GIP on _app level, then we use the giapState.
hydrate(store, gsspState ?? gippState ?? giapState);
}
};

const useHybridHydrate = (store: S, gipState: any, gspState: any, gsspState: any) => {
const useHybridHydrate = (store: S, giapState: any, gspState: any, gsspState: any, gippState: any) => {
const {events} = useRouter();
const shouldHydrate = useRef(true);

Expand Down Expand Up @@ -203,27 +204,36 @@ export const createWrapper = <S extends Store>(makeStore: MakeStore<S>, config:
// Instead, React will render the new page components straight away, which will have selectors with the correct data.
useMemo(() => {
if (shouldHydrate.current) {
hydrateOrchestrator(store, gipState, gspState, gsspState);
hydrateOrchestrator(store, giapState, gspState, gsspState, gippState);
shouldHydrate.current = false;
}
}, [store, gipState, gspState, gsspState]);
}, [store, giapState, gspState, gsspState, gippState]);
};

const useWrappedStore = ({initialState, initialProps, ...props}: any, displayName = 'useWrappedStore'): {store: S; props: any} => {
// giapState stands for getInitialAppProps state
const useWrappedStore = (
{initialState: giapState, initialProps, ...props}: any,
displayName = 'useWrappedStore',
): {store: S; props: any} => {
// getStaticProps state
const gspState = props?.__N_SSG ? props?.pageProps?.initialState : null;
// getServerSideProps state
const gsspState = props?.__N_SSP ? props?.pageProps?.initialState : null;
// getInitialPageProps state
const gippState = !gspState && !gsspState ? props?.pageProps?.initialState ?? null : null;

if (config.debug) {
console.log('4.', displayName, 'created new store with', {
initialState,
giapState,
gspState,
gsspState,
gippState,
});
}

const store = useMemo<S>(() => initStore<S>({makeStore}), []);

useHybridHydrate(store, initialState, gspState, gsspState);
useHybridHydrate(store, giapState, gspState, gsspState, gippState);

let resultProps: any = props;

Expand Down

0 comments on commit 983e750

Please sign in to comment.