Skip to content

Commit

Permalink
Prevent _app.js to be wrapped with default wrapper
Browse files Browse the repository at this point in the history
Fix #232
  • Loading branch information
kirill-konshin committed Jun 1, 2020
1 parent 07e012d commit 45f35e6
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 56 deletions.
27 changes: 11 additions & 16 deletions README.md
Expand Up @@ -129,16 +129,13 @@ export const wrapper = createWrapper(makeStore, {debug: true});
It is highly recommended to use `pages/_app` to wrap all pages at once, otherwise due to potential race conditions you may get `Cannot update component while rendering another component`:

```typescript
import React from 'react';
import App, {AppInitialProps} from 'next/app';
import React, {FC} from 'react';
import {AppProps} from 'next/app';
import {wrapper} from '../components/store';

class WrappedApp extends App<AppInitialProps> {
public render() {
const {Component, pageProps} = this.props;
return <Component {...pageProps} />;
}
}
const WrappedApp: FC<AppProps> = ({Component, pageProps}) => (
<Component {...pageProps} />
);

export default wrapper.withRedux(WrappedApp);
```
Expand All @@ -148,20 +145,18 @@ export default wrapper.withRedux(WrappedApp);

```js
import React from 'react';
import App from 'next/app';
import {wrapper} from '../components/store';

class WrappedApp extends App {
public render() {
const {Component, pageProps} = this.props;
return <Component {...pageProps} />;
}
}
const MyApp = ({Component, pageProps}) => (
<Component {...pageProps} />
);

export default wrapper.withRedux(WrappedApp);
export default wrapper.withRedux(MyApp, {wrapDefaultGetInitialProps: true});
```
</details>

:warning: Next.js provides [generic `getInitialProps`](https://github.com/vercel/next.js/blob/canary/packages/next/pages/_app.tsx#L21) when using `class MyApp extends App` which will be picked up by wrapper, so you **must not extend `App`** as you'll be opted out of Automatic Static Optimization: https://err.sh/next.js/opt-out-auto-static-optimization. Just export a regular Functional Component as in the example above.

## State reconciliation during hydration

Each time when pages that have `getStaticProps` or `getServerSideProps` are opened by user the `HYDRATE` action will be dispatched. This may happen during initial page load and during regular page navigation. The `payload` of this action will contain the `state` at the moment of static generation or server side rendering, so your reducer must merge it with existing client state properly.
Expand Down
11 changes: 3 additions & 8 deletions packages/demo-page/src/pages/_app.tsx
@@ -1,12 +1,7 @@
import React from 'react';
import App, {AppInitialProps} from 'next/app';
import React, {FC} from 'react';
import {AppProps} from 'next/app';
import {wrapper} from '../components/store';

class WrappedApp extends App<AppInitialProps> {
public render() {
const {Component, pageProps} = this.props;
return <Component {...pageProps} />;
}
}
const WrappedApp: FC<AppProps> = ({Component, pageProps}) => <Component {...pageProps} />;

export default wrapper.withRedux(WrappedApp);
36 changes: 11 additions & 25 deletions packages/demo-page/src/pages/_error.tsx
@@ -1,31 +1,17 @@
import React, {Component} from 'react';
import React from 'react';
import Link from 'next/link';
import {NextPageContext} from 'next';
import {connect} from 'react-redux';
import {State} from '../components/reducer';

class ErrorPage extends Component<State> {
public static getInitialProps = ({store, pathname}: NextPageContext) => {
console.log('2. Page.getInitialProps uses the store to dispatch things');
store.dispatch({type: 'PAGE', payload: 'was set in error page ' + pathname});
return {};
};

render() {
const {page} = this.props;
return (
<>
<p>
This is an error page, it also has access to store: <code>{page}</code>
</p>
<nav>
<Link href="/">
<a>Navigate to index</a>
</Link>
</nav>
</>
);
}
}
const ErrorPage = ({page}: any) => (
<>
<p>This is an error page, {page}.</p>
<nav>
<Link href="/">
<a>Navigate to index</a>
</Link>
</nav>
</>
);

export default connect((state: State) => state)(ErrorPage);
20 changes: 13 additions & 7 deletions packages/wrapper/src/index.tsx
Expand Up @@ -165,10 +165,11 @@ export const createWrapper = <S extends {} = any, A extends Action = AnyAction>(
const store = useRef<Store<S, A>>(initStore({makeStore, config, context}));

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

// ATTENTION! This code assumes that Page's getServerSideProps is executed after App.getInitialProps
// so we dispatch in this order
Expand Down Expand Up @@ -199,12 +200,17 @@ export const createWrapper = <S extends {} = any, A extends Action = AnyAction>(
...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;
let resultProps = props;

// just some cleanup to prevent passing it as props, we need to clone props to safely delete initialState
if (initialStateFromGSPorGSSR) {
resultProps = {...props, pageProps: {...props.pageProps}};
delete resultProps.pageProps.initialState;
}

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

0 comments on commit 45f35e6

Please sign in to comment.