Skip to content

Commit

Permalink
Merge 2f10b94 into cd34b26
Browse files Browse the repository at this point in the history
  • Loading branch information
kirill-konshin committed Nov 1, 2022
2 parents cd34b26 + 2f10b94 commit f2adf48
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 50 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ import {AppProps} from 'next/app';
import {wrapper} from '../components/store';

const MyApp: FC<AppProps> = ({Component, ...rest}) => {
const {store, props} = wrapper.useWrappedStore(rest);
const {store, props} = wrapper.useWrappedStore({Component, ...rest});
return (
<Provider store={store}>
<Component {...props.pageProps} />
Expand Down
2 changes: 1 addition & 1 deletion packages/demo-redux-toolkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"start": "next --port=6060"
},
"dependencies": {
"@reduxjs/toolkit": "1.6.2",
"@reduxjs/toolkit": "1.8.6",
"next-redux-wrapper": "*",
"react": "17.0.2",
"react-dom": "17.0.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/demo-redux-toolkit/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {AppProps} from 'next/app';
import {wrapper} from '../store';

const MyApp: FC<AppProps> = ({Component, ...rest}) => {
const {store, props} = wrapper.useWrappedStore(rest);
const {store, props} = wrapper.useWrappedStore({Component, ...rest});
return (
<Provider store={store}>
<Component {...props.pageProps} />
Expand Down
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 @@ -11,6 +11,10 @@ export default function IndexPage() {
<Link href="/subject/1">
<a>Go to problem pages</a>
</Link>
<br />
<Link href="/pokemon/pikachu">
<a>Go to Pokemon</a>
</Link>
</div>
);
}
35 changes: 35 additions & 0 deletions packages/demo-redux-toolkit/pages/pokemon/[pokemon].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { useRouter } from "next/router";
import { wrapper, pokemonApi, useGetPokemonByNameQuery } from "../../store";
import {useStore} from "react-redux";

export default function Pokemon() {
const { query } = useRouter();

console.log('State on render', useStore().getState());
const { data } = useGetPokemonByNameQuery(query.pokemon as string); // data is undefined for the first render

if (!data) {
throw new Error("Data is undefined when page is opened by client routing");
}

return <div>Name: {data?.name}</div>;
}

export const getServerSideProps = wrapper.getServerSideProps(
(store) => async (context) => {
const pokemon = context.params?.pokemon;
if (typeof pokemon === "string") {
console.log('DISPATCH');
store.dispatch(pokemonApi.endpoints.getPokemonByName.initiate(pokemon));
}

await Promise.all(pokemonApi.util.getRunningOperationPromises());

console.log('SERVER STATE', store.getState().pokemonApi);

return {
props: {},
};
}
);
2 changes: 1 addition & 1 deletion packages/demo-redux-toolkit/pages/subject/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const Page = props => {
console[content ? 'info' : 'warn']('Rendered content: ', content);

if (!content) {
return <div>RENDERED WITHOUT CONTENT FROM STORE!!!???</div>;
throw new Error("Data is undefined when page is opened by client routing");
}

return (
Expand Down
27 changes: 27 additions & 0 deletions packages/demo-redux-toolkit/store.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {configureStore, createSlice, ThunkAction} from '@reduxjs/toolkit';
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import {Action} from 'redux';
import {createWrapper, HYDRATE} from 'next-redux-wrapper';

// Slice approach
export const subjectSlice = createSlice({
name: 'subject',

Expand All @@ -24,12 +26,36 @@ export const subjectSlice = createSlice({
},
});

export type Pokemon = {
name: string;
};

// API approach
export const pokemonApi = createApi({
reducerPath: "pokemonApi",
baseQuery: fetchBaseQuery({ baseUrl: "https://pokeapi.co/api/v2" }),
extractRehydrationInfo(action, { reducerPath }) {
if (action.type === HYDRATE) {
return action.payload[reducerPath];
}
},
endpoints: (builder) => ({
getPokemonByName: builder.query<Pokemon, string>({
query: (name) => `/pokemon/${name}`,
}),
}),
});

export const { useGetPokemonByNameQuery } = pokemonApi;

const makeStore = () =>
configureStore({
reducer: {
[subjectSlice.name]: subjectSlice.reducer,
[pokemonApi.reducerPath]: pokemonApi.reducer,
},
devTools: true,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(pokemonApi.middleware),
});

export type AppStore = ReturnType<typeof makeStore>;
Expand All @@ -48,6 +74,7 @@ export const fetchSubject =
[id]: {
id,
name: `Subject ${id}`,
random: Math.random()
},
}),
);
Expand Down
38 changes: 22 additions & 16 deletions packages/wrapper/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import App, {AppContext, AppInitialProps} from 'next/app';
import { useRouter } from 'next/router';
import React, {useEffect, useMemo, useRef} from 'react';
import {Provider} from 'react-redux';
import {Store} from 'redux';
Expand Down Expand Up @@ -161,29 +162,34 @@ export const createWrapper = <S extends Store>(makeStore: MakeStore<S>, config:
} as any);
};

const useHybridHydrate = (store: S, state: any) => {
const firstRender = useRef<boolean>(true);
const useHybridHydrate = (store: S, state: any, Component: any) => {
const prevRoute = useRef<string>('');
const prevComponent = useRef<string>('');

useEffect(() => {
firstRender.current = false;
}, []);
const { asPath } = useRouter();

const newPage = prevRoute.current !== asPath || prevComponent !== Component;

prevRoute.current = asPath;
prevComponent.current = Component;

// synchronous for server or first time render
useMemo(() => {
// synchronous for server or first time render
if (getIsServer() || firstRender.current) {
if (newPage) {
hydrate(store, state);
}
}, [store, state]);
}, [store, state, newPage]);

// asynchronous for client subsequent navigation
useEffect(() => {
// asynchronous for client subsequent navigation
if (!getIsServer()) {
// FIXME Here we assume that if path has not changed, the component used to render the path has not changed either, so we can hydrate asynchronously
if (!newPage) {
hydrate(store, state);
}
}, [store, state]);
}, [store, state, newPage]);
};

const useWrappedStore = ({initialState, initialProps, ...props}: any, displayName = 'useWrappedStore'): {store: S; props: any} => {
const useWrappedStore = ({Component, initialState, initialProps, ...props}: any, displayName = 'useWrappedStore'): {store: S; props: any} => {
// this happens when App has page with getServerSideProps/getStaticProps, initialState will be dumped twice:
// one incomplete and one complete
const initialStateFromGSPorGSSR = props?.pageProps?.initialState;
Expand All @@ -197,8 +203,8 @@ export const createWrapper = <S extends Store>(makeStore: MakeStore<S>, config:

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

useHybridHydrate(store, initialState);
useHybridHydrate(store, initialStateFromGSPorGSSR);
useHybridHydrate(store, initialState, Component);
useHybridHydrate(store, initialStateFromGSPorGSSR, Component);

let resultProps: any = props;

Expand All @@ -223,7 +229,7 @@ export const createWrapper = <S extends Store>(makeStore: MakeStore<S>, config:
delete resultProps.pageProps.initialProps;
}

return {store, props: {...initialProps, ...resultProps}};
return {store, props: {...initialProps, ...resultProps, Component}};
};

const withRedux = (Component: NextComponentType | App | any) => {
Expand All @@ -233,7 +239,7 @@ export const createWrapper = <S extends Store>(makeStore: MakeStore<S>, config:

//TODO Check if pages/_app was wrapped so there's no need to wrap a page itself
const WrappedComponent = (props: any) => {
const {store, props: combinedProps} = useWrappedStore(props, WrappedComponent.displayName);
const {store, props: combinedProps} = useWrappedStore({Component, ...props}, WrappedComponent.displayName); // Component goes first for _app which has props.component

return (
<Provider store={store}>
Expand Down
6 changes: 3 additions & 3 deletions packages/wrapper/tests/client.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import * as React from 'react';
import {useDispatch} from 'react-redux';
import {create, act} from 'react-test-renderer';
import {DummyComponent, wrapper, child, makeStore} from './testlib';
import {DummyComponent, wrapper, child, makeStore, Router} from './testlib';
import {createWrapper} from '../src';
import {Store} from 'redux';

Expand All @@ -22,7 +22,7 @@ describe('client integration', () => {

test('withRedux', async () => {
const WrappedPage: any = wrapper.withRedux(DummyComponent);
expect(child(<WrappedPage initialState={store.getState()} />)).toEqual('{"props":{},"state":{"reduxStatus":"init"}}');
expect(child(<Router><WrappedPage initialState={store.getState()} /></Router>)).toEqual('{"props":{},"state":{"reduxStatus":"init"}}');
});

test('API functions', async () => {
Expand Down Expand Up @@ -57,7 +57,7 @@ describe('client integration', () => {
const Wrapped: any = w.withRedux(Page);

act(() => {
create(<Wrapped />);
create(<Router><Wrapped /></Router>);
});

// expected when invoked above
Expand Down
12 changes: 6 additions & 6 deletions packages/wrapper/tests/server.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import {createWrapper} from '../src';
import {child, DummyComponent, makeStore} from './testlib';
import {child, DummyComponent, makeStore, Router} from './testlib';

describe('function API', () => {
const ctx: any = {req: {request: true}};
Expand Down Expand Up @@ -98,7 +98,7 @@ describe('function API', () => {

const WrappedPage: any = wrapper.withRedux(DummyComponent);

expect(child(<WrappedPage {...resultingProps} />)).toEqual(
expect(child(<Router><WrappedPage {...resultingProps} /></Router>)).toEqual(
'{"props":{"pageProps":{"fromApp":true,"fromSSP":true}},"state":{"reduxStatus":"ssp"}}',
);
});
Expand All @@ -124,19 +124,19 @@ describe('withRedux', () => {
describe('merges props', () => {
test('for page case', () => {
const WrappedPage: any = createWrapper(makeStore).withRedux(DummyComponent);
expect(child(<WrappedPage initialProps={{fromPage: true}} somePropFromNextJs={true} />)).toEqual(
expect(child(<Router><WrappedPage initialProps={{fromPage: true}} somePropFromNextJs={true} /></Router>)).toEqual(
'{"props":{"fromPage":true,"somePropFromNextJs":true},"state":{"reduxStatus":"init"}}',
);
});
test('for app case', () => {
const WrappedApp: any = createWrapper(makeStore).withRedux(DummyComponent);
expect(child(<WrappedApp initialProps={{pageProps: {fromApp: true}}} pageProps={{getStaticProp: true}} />)).toEqual(
expect(child(<Router><WrappedApp initialProps={{pageProps: {fromApp: true}}} pageProps={{getStaticProp: true}} /></Router>)).toEqual(
'{"props":{"pageProps":{"fromApp":true,"getStaticProp":true}},"state":{"reduxStatus":"init"}}',
);
});
test('for page case (new Next versions)', () => {
const WrappedPage: any = createWrapper(makeStore).withRedux(DummyComponent);
expect(child(<WrappedPage pageProps={{initialProps: {fromPage: true}}} somePropFromNextJs={true} />)).toEqual(
expect(child(<Router><WrappedPage pageProps={{initialProps: {fromPage: true}}} somePropFromNextJs={true} /></Router>)).toEqual(
'{"props":{"pageProps":{"fromPage":true},"somePropFromNextJs":true},"state":{"reduxStatus":"init"}}',
);
});
Expand Down Expand Up @@ -169,7 +169,7 @@ describe('custom serialization', () => {

const WrappedApp: any = wrapper.withRedux(DummyComponent);

expect(child(<WrappedApp {...props} />)).toEqual(
expect(child(<Router><WrappedApp {...props} /></Router>)).toEqual(
'{"props":{},"state":{"reduxStatus":"init","serialized":true,"deserialized":true}}',
);
});
Expand Down
4 changes: 4 additions & 0 deletions packages/wrapper/tests/testlib.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {applyMiddleware, createStore, AnyAction} from 'redux';
import {useSelector} from 'react-redux';
import promiseMiddleware from 'redux-promise-middleware';
import {createWrapper, HYDRATE} from '../src';
import {RouterContext} from 'next/dist/shared/lib/router-context';

export interface State {
reduxStatus?: string;
Expand Down Expand Up @@ -34,3 +35,6 @@ export const DummyComponent: React.ComponentType<any> = (props: any) => {
};

export const child = (cmp: any) => (create(cmp)?.toJSON() as any)?.children?.[0];

export const Router = ({asPath = '/foo', children}: any) =>
<RouterContext.Provider value={{asPath} as any}>{children}</RouterContext.Provider>
49 changes: 28 additions & 21 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2937,15 +2937,15 @@
resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204"
integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg==

"@reduxjs/toolkit@1.6.2":
version "1.6.2"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.6.2.tgz#2f2b5365df77dd6697da28fdf44f33501ed9ba37"
integrity sha512-HbfI/hOVrAcMGAYsMWxw3UJyIoAS9JTdwddsjlr5w3S50tXhWb+EMyhIw+IAvCVCLETkzdjgH91RjDSYZekVBA==
"@reduxjs/toolkit@1.8.6":
version "1.8.6"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.8.6.tgz#147fb7957befcdb75bc9c1230db63628e30e4332"
integrity sha512-4Ia/Loc6WLmdSOzi7k5ff7dLK8CgG2b8aqpLsCAJhazAzGdp//YBUSaj0ceW6a3kDBDNRrq5CRwyCS0wBiL1ig==
dependencies:
immer "^9.0.6"
redux "^4.1.0"
redux-thunk "^2.3.0"
reselect "^4.0.0"
immer "^9.0.7"
redux "^4.1.2"
redux-thunk "^2.4.1"
reselect "^4.1.5"

"@rushstack/eslint-patch@1.1.0", "@rushstack/eslint-patch@^1.1.0":
version "1.1.0"
Expand Down Expand Up @@ -6333,10 +6333,10 @@ image-size@1.0.0:
dependencies:
queue "6.0.2"

immer@^9.0.6:
version "9.0.7"
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.7.tgz#b6156bd7db55db7abc73fd2fdadf4e579a701075"
integrity sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA==
immer@^9.0.7:
version "9.0.16"
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.16.tgz#8e7caab80118c2b54b37ad43e05758cdefad0198"
integrity sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==

import-fresh@^3.0.0, import-fresh@^3.2.1:
version "3.2.1"
Expand Down Expand Up @@ -9306,12 +9306,12 @@ redux-saga@1.1.3:
dependencies:
"@redux-saga/core" "^1.1.3"

redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
redux-thunk@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714"
integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==

redux@4.1.2, redux@^4.1.0:
redux@4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104"
integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==
Expand All @@ -9326,6 +9326,13 @@ redux@^4.0.0, redux@^4.0.4:
loose-envify "^1.4.0"
symbol-observable "^1.2.0"

redux@^4.1.2:
version "4.2.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13"
integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==
dependencies:
"@babel/runtime" "^7.9.2"

regenerate-unicode-properties@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326"
Expand Down Expand Up @@ -9423,10 +9430,10 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=

reselect@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
reselect@^4.1.5:
version "4.1.6"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.6.tgz#19ca2d3d0b35373a74dc1c98692cdaffb6602656"
integrity sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ==

resolve-cwd@^3.0.0:
version "3.0.0"
Expand Down

0 comments on commit f2adf48

Please sign in to comment.