From be23ed3a1395b5d867808e35b7ed606bd9cf6a26 Mon Sep 17 00:00:00 2001 From: Kirill Konshin Date: Mon, 31 Oct 2022 18:02:07 -0700 Subject: [PATCH] Fix #493 #495 #496 --- packages/demo-redux-toolkit/package.json | 2 +- packages/demo-redux-toolkit/pages/index.tsx | 4 ++ .../pages/pokemon/[pokemon].tsx | 35 +++++++++++++ .../demo-redux-toolkit/pages/subject/[id].tsx | 2 +- packages/demo-redux-toolkit/store.tsx | 27 ++++++++++ packages/wrapper/src/index.tsx | 25 ++++++---- yarn.lock | 49 +++++++++++-------- 7 files changed, 111 insertions(+), 33 deletions(-) create mode 100644 packages/demo-redux-toolkit/pages/pokemon/[pokemon].tsx 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"