diff --git a/packages/demo-redux-toolkit/package.json b/packages/demo-redux-toolkit/package.json
index affed4a..dafd432 100644
--- a/packages/demo-redux-toolkit/package.json
+++ b/packages/demo-redux-toolkit/package.json
@@ -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",
diff --git a/packages/demo-redux-toolkit/pages/index.tsx b/packages/demo-redux-toolkit/pages/index.tsx
index 51d9373..2ce3f88 100644
--- a/packages/demo-redux-toolkit/pages/index.tsx
+++ b/packages/demo-redux-toolkit/pages/index.tsx
@@ -11,6 +11,10 @@ export default function IndexPage() {
Go to problem pages
+
+
+ Go to Pokemon
+
);
}
diff --git a/packages/demo-redux-toolkit/pages/pokemon/[pokemon].tsx b/packages/demo-redux-toolkit/pages/pokemon/[pokemon].tsx
new file mode 100644
index 0000000..005dbe7
--- /dev/null
+++ b/packages/demo-redux-toolkit/pages/pokemon/[pokemon].tsx
@@ -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
Name: {data?.name}
;
+}
+
+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: {},
+ };
+ }
+);
diff --git a/packages/demo-redux-toolkit/pages/subject/[id].tsx b/packages/demo-redux-toolkit/pages/subject/[id].tsx
index b1cd43d..acb6653 100644
--- a/packages/demo-redux-toolkit/pages/subject/[id].tsx
+++ b/packages/demo-redux-toolkit/pages/subject/[id].tsx
@@ -10,7 +10,7 @@ const Page = props => {
console[content ? 'info' : 'warn']('Rendered content: ', content);
if (!content) {
- return RENDERED WITHOUT CONTENT FROM STORE!!!???
;
+ throw new Error("Data is undefined when page is opened by client routing");
}
return (
diff --git a/packages/demo-redux-toolkit/store.tsx b/packages/demo-redux-toolkit/store.tsx
index fedce70..dfac7d1 100644
--- a/packages/demo-redux-toolkit/store.tsx
+++ b/packages/demo-redux-toolkit/store.tsx
@@ -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',
@@ -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({
+ 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;
@@ -48,6 +74,7 @@ export const fetchSubject =
[id]: {
id,
name: `Subject ${id}`,
+ random: Math.random()
},
}),
);
diff --git a/packages/wrapper/src/index.tsx b/packages/wrapper/src/index.tsx
index 99db206..0144afb 100644
--- a/packages/wrapper/src/index.tsx
+++ b/packages/wrapper/src/index.tsx
@@ -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';
@@ -162,25 +163,29 @@ export const createWrapper = (makeStore: MakeStore, config:
};
const useHybridHydrate = (store: S, state: any) => {
- const firstRender = useRef(true);
+ const prevRoute = useRef('');
+ const prevState = useRef();
- useEffect(() => {
- firstRender.current = false;
- }, []);
+ const { asPath } = useRouter();
+
+ const newPath = prevRoute.current !== asPath;
+
+ prevRoute.current = asPath;
+ // synchronous for server or first time render
useMemo(() => {
- // synchronous for server or first time render
- if (getIsServer() || firstRender.current) {
+ if (newPath && prevState.current !== state) {
hydrate(store, state);
}
- }, [store, state]);
+ }, [store, state, newPath]);
+ // 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 (!newPath && prevState.current !== state) {
hydrate(store, state);
}
- }, [store, state]);
+ }, [store, state, newPath]);
};
const useWrappedStore = ({initialState, initialProps, ...props}: any, displayName = 'useWrappedStore'): {store: S; props: any} => {
diff --git a/yarn.lock b/yarn.lock
index fc012a8..f509fc2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"
@@ -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"
@@ -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==
@@ -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"
@@ -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"