Skip to content

Commit

Permalink
6.0.0-rc.8
Browse files Browse the repository at this point in the history
- Fix for static state not applied on navigation
- Redux logger in demos
  • Loading branch information
kirill-konshin committed Apr 22, 2020
1 parent 67e6a53 commit 4d4f9fb
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 76 deletions.
4 changes: 3 additions & 1 deletion packages/demo-page/package.json
Expand Up @@ -16,7 +16,8 @@
"react": "16.13.1",
"react-dom": "16.13.1",
"react-redux": "7.2.0",
"redux": "4.0.5"
"redux": "4.0.5",
"redux-logger": "3.0.6"
},
"devDependencies": {
"@types/expect-puppeteer": "4.4.0",
Expand All @@ -26,6 +27,7 @@
"@types/react": "16.9.31",
"@types/react-dom": "16.9.6",
"@types/react-redux": "7.1.7",
"@types/redux-logger": "3.0.7",
"@types/webpack-env": "1.15.1",
"jest": "25.2.4",
"jest-puppeteer": "4.4.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/demo-page/src/components/store.tsx
@@ -1,9 +1,10 @@
import {createStore} from 'redux';
import {createStore, applyMiddleware} from 'redux';
import logger from 'redux-logger';
import {MakeStore, createWrapper, Context} from 'next-redux-wrapper';
import reducer, {State} from './reducer';

export const makeStore: MakeStore<State> = (context: Context) => {
const store = createStore(reducer);
const store = createStore(reducer, applyMiddleware(logger));

if (module.hot) {
module.hot.accept('./reducer', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/demo-saga/package.json
Expand Up @@ -17,6 +17,7 @@
"react-dom": "16.13.1",
"react-redux": "7.2.0",
"redux": "4.0.5",
"redux-logger": "3.0.6",
"redux-saga": "1.1.3"
},
"devDependencies": {
Expand All @@ -28,6 +29,7 @@
"@types/react": "16.9.31",
"@types/react-dom": "16.9.6",
"@types/react-redux": "7.1.7",
"@types/redux-logger": "3.0.7",
"@types/webpack-env": "1.15.1",
"jest": "25.2.4",
"jest-puppeteer": "4.4.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/demo-saga/src/components/store.tsx
@@ -1,6 +1,7 @@
import {createStore, applyMiddleware, Store} from 'redux';
import {MakeStore, Context} from 'next-redux-wrapper';
import logger from 'redux-logger';
import createSagaMiddleware, {Task} from 'redux-saga';
import {MakeStore, Context} from 'next-redux-wrapper';
import reducer, {State} from './reducer';
import rootSaga from './saga';

Expand All @@ -13,7 +14,7 @@ export const makeStore: MakeStore<State> = (context: Context) => {
const sagaMiddleware = createSagaMiddleware();

// 2: Add an extra parameter for applying middleware:
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
const store = createStore(reducer, applyMiddleware(sagaMiddleware, logger));

// 3: Run your sagas on server
(store as SagaStore).sagaTask = sagaMiddleware.run(rootSaga);
Expand Down
4 changes: 3 additions & 1 deletion packages/demo/package.json
Expand Up @@ -15,7 +15,8 @@
"react": "16.13.1",
"react-dom": "16.13.1",
"react-redux": "7.2.0",
"redux": "4.0.5"
"redux": "4.0.5",
"redux-logger": "3.0.6"
},
"devDependencies": {
"@types/expect-puppeteer": "4.4.0",
Expand All @@ -25,6 +26,7 @@
"@types/react": "16.9.31",
"@types/react-dom": "16.9.6",
"@types/react-redux": "7.1.7",
"@types/redux-logger": "3.0.7",
"@types/webpack-env": "1.15.1",
"jest": "25.2.4",
"jest-puppeteer": "4.4.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/demo/src/components/store.tsx
@@ -1,9 +1,10 @@
import {createStore} from 'redux';
import {createStore, applyMiddleware} from 'redux';
import logger from 'redux-logger';
import {MakeStore, createWrapper, Context} from 'next-redux-wrapper';
import reducer, {State} from './reducer';

export const makeStore: MakeStore<State> = (context: Context) => {
const store = createStore(reducer);
const store = createStore(reducer, applyMiddleware(logger));

if (module.hot) {
module.hot.accept('./reducer', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/demo/src/pages/static.tsx
Expand Up @@ -10,7 +10,7 @@ interface OtherProps {
appProp: string;
}

const Other: NextPage<OtherProps> = ({appProp, getStaticProp}) => {
const Static: NextPage<OtherProps> = ({appProp, getStaticProp}) => {
const {app, page} = useSelector<State, State>(state => state);
return (
<div className="static">
Expand All @@ -32,4 +32,4 @@ export const getStaticProps = wrapper.getStaticProps(({store}) => {
return {props: {getStaticProp: 'bar'}};
});

export default Other;
export default Static;
11 changes: 11 additions & 0 deletions packages/demo/tests/index.spec.ts
Expand Up @@ -48,4 +48,15 @@ describe('Using App wrapper', () => {
await expect(page).toMatch('"page": "static"'); // redux
await expect(page).toMatch('"app": "was set in _app"'); // redux
});

it('other page -> static', async () => {
await openPage('/');

await page.waitForSelector('div.index');

await expect(page).toClick('a', {text: 'Navigate to static'});

await expect(page).toMatch('"page": "static"'); // redux
await expect(page).toMatch('"app": "was set in _app"'); // redux
});
});
130 changes: 64 additions & 66 deletions packages/wrapper/src/index.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import React, {useCallback, useEffect, useRef} from 'react';
import {Store, AnyAction, Action} from 'redux';
import {Provider} from 'react-redux';
import {GetServerSideProps, GetStaticProps, NextComponentType, NextPageContext} from 'next';
import {GetServerSideProps, GetStaticProps, NextComponentType, NextPage, NextPageContext} from 'next';
import App, {AppContext, AppInitialProps} from 'next/app';
import {IncomingMessage, ServerResponse} from 'http';
import {ParsedUrlQuery} from 'querystring';
Expand All @@ -24,7 +24,7 @@ export declare type MakeStore<S = any, A extends Action = AnyAction> = (context:
export interface InitStoreOptions<S = any, A extends Action = AnyAction> {
makeStore: MakeStore<S, A>;
context: Context;
config: Config;
config: Config<S>;
}

const initStore = <S extends {} = any, A extends Action = AnyAction>({
Expand Down Expand Up @@ -77,7 +77,7 @@ export interface GetStaticPropsContext {

export const createWrapper = <S extends {} = any, A extends Action = AnyAction>(
makeStore: MakeStore<S, A>,
config: Config = {},
config: Config<S> = {},
) => {
const makeProps = async ({
callback,
Expand Down Expand Up @@ -147,77 +147,75 @@ export const createWrapper = <S extends {} = any, A extends Action = AnyAction>(
): GetServerSideProps<P> => getStaticProps<P>(callback as any) as any; // just not to repeat myself

const withRedux = (Component: NextComponentType | App | any) => {
const hasGetInitialProps = 'getInitialProps' in Component;
const displayName = `withRedux(${Component.displayName || Component.name || 'Component'})`;
//TODO Check if pages/_app was wrapped so there's no need to wrap a page itself
const Wrapper: NextPage<WrapperProps> = ({initialState, initialProps, ...props}, context) => {
const isFirstRender = useRef<boolean>(true);

class Wrapper extends React.Component<WrapperProps> {
public store: Store<S, A>;
// this happens when App has page with getServerSideProps/getStaticProps
const initialStateFromGSPorGSSR = props?.pageProps?.initialState;

public constructor(props: WrapperProps, context: AppContext) {
super(props, context);
if (config.debug)
console.log('4. WrappedApp.constructor created new store with', {
initialState,
initialStateFromGSPorGSSR,
});

const {initialState} = props;
const store = useRef<Store<S, A>>(initStore({makeStore, config, context}));

if (config.debug)
console.log('4. WrappedApp.constructor created new store with initialState', initialState);

//TODO Check if pages/_app was wrapped and there's no need to wrap a page itself
this.store = initStore({makeStore, config, context});

this.store.dispatch({
const hydrate = useCallback(() => {
store.current.dispatch({
type: HYDRATE,
payload: getDeserializedState(initialState, config),
} as any);

if (props?.pageProps?.initialState) {
this.store.dispatch({
// ATTENTION! This code assumes that Page's getServerSideProps is executed after App.getInitialProps
// so we dispatch in this order
if (initialStateFromGSPorGSSR)
store.current.dispatch({
type: HYDRATE,
payload: getDeserializedState(
// this happens when App has page with getServerSideProps/getStaticProps
// ATTENTION! This code assumes that Page's getServerSideProps is executed after App.getInitialProps
props.pageProps.initialState,
config,
),
payload: getDeserializedState(initialStateFromGSPorGSSR, config),
} as any);
}, [initialStateFromGSPorGSSR, initialState]);

// apply synchronously on first render (both server side and client side)
if (isFirstRender.current) hydrate();

// apply async in case props have changed, on navigation for example
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
}

public render() {
const {initialState, initialProps, ...props} = this.props as any;

// order is important! Next.js overwrites props from pages/_app with getStaticProps from page
// @see https://github.com/zeit/next.js/issues/11648
if (initialProps && initialProps.pageProps)
props.pageProps = {
...initialProps.pageProps, // this comes from wrapper in _app mode
...props.pageProps, // this comes from gssp/gsp in _app mode
};

if (props.pageProps) {
// this happens when App has page with getServerSideProps
// just some cleanup here
delete props.pageProps.initialState;
}
hydrate();
}, [hydrate]);

// order is important! Next.js overwrites props from pages/_app with getStaticProps from page
// @see https://github.com/zeit/next.js/issues/11648
if (initialProps && initialProps.pageProps)
props.pageProps = {
...initialProps.pageProps, // this comes from wrapper in _app mode
...props.pageProps, // this comes from gssp/gsp in _app mode
};

// just some cleanup to prevent passing it as props
if (initialStateFromGSPorGSSR) delete props.pageProps.initialState;

return (
<Provider store={store.current}>
<Component {...initialProps} {...props} />
</Provider>
);
};

return (
<Provider store={this.store}>
<Component {...initialProps} {...props} />
</Provider>
);
}
}
Wrapper.displayName = `withRedux(${Component.displayName || Component.name || 'Component'})`;

if ('getInitialProps' in Component)
Wrapper.getInitialProps = async (context: any) => {
const callback = Component.getInitialProps; // bind?
return (context.ctx ? getInitialAppProps(callback) : getInitialPageProps(callback))(context);
};

return hasGetInitialProps
? class WrappedCmp extends Wrapper {
public static displayName = displayName;
public static getInitialProps = async (context: any) => {
const callback = Component.getInitialProps; // bind?
return (context.ctx ? getInitialAppProps(callback) : getInitialPageProps(callback))(context);
};
}
: class WrappedCmp extends Wrapper {
public static displayName = displayName;
};
return Wrapper;
};

return {
Expand All @@ -230,16 +228,16 @@ export const createWrapper = <S extends {} = any, A extends Action = AnyAction>(
// Legacy
export default <S extends {} = any, A extends Action = AnyAction>(makeStore: MakeStore<S, A>, config: Config = {}) => {
console.warn(
'/!\\ You are using legacy implementaion. Please update your code: use createWrapper and wrapper.withRedux.',
'/!\\ You are using legacy implementaion. Please update your code: use createWrapper() and wrapper.withRedux().',
);
return createWrapper(makeStore, config).withRedux;
};

export type Context = NextPageContext | AppContext | GetStaticPropsContext | GetServerSidePropsContext;

export interface Config {
serializeState?: (state: any) => any;
deserializeState?: (state: any) => any;
export interface Config<S extends {} = any> {
serializeState?: (state: S) => any;
deserializeState?: (state: any) => S;
storeKey?: string;
debug?: boolean;
}
Expand Down
19 changes: 19 additions & 0 deletions yarn.lock
Expand Up @@ -2542,6 +2542,13 @@
"@types/prop-types" "*"
csstype "^2.2.0"

"@types/redux-logger@3.0.7":
version "3.0.7"
resolved "https://registry.yarnpkg.com/@types/redux-logger/-/redux-logger-3.0.7.tgz#163f6f6865c69c21d56f9356dc8d741718ec0db0"
integrity sha512-oV9qiCuowhVR/ehqUobWWkXJjohontbDGLV88Be/7T4bqMQ3kjXwkFNL7doIIqlbg3X2PC5WPziZ8/j/QHNQ4A==
dependencies:
redux "^3.6.0"

"@types/redux-promise-middleware@6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@types/redux-promise-middleware/-/redux-promise-middleware-6.0.0.tgz#df30dc25396d731abf8289111d2399609cb5bd47"
Expand Down Expand Up @@ -4434,6 +4441,11 @@ dedent@^0.7.0:
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=

deep-diff@^0.3.5:
version "0.3.8"
resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84"
integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=

deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
Expand Down Expand Up @@ -9585,6 +9597,13 @@ redent@^2.0.0:
indent-string "^3.0.0"
strip-indent "^2.0.0"

redux-logger@3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf"
integrity sha1-91VZZvMJjzyIYExEnPC69XeCdL8=
dependencies:
deep-diff "^0.3.5"

redux-promise-middleware@*, redux-promise-middleware@6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/redux-promise-middleware/-/redux-promise-middleware-6.1.2.tgz#1c14222686934be243cbb292e348ef7d5b20d6d2"
Expand Down

0 comments on commit 4d4f9fb

Please sign in to comment.