Skip to content

Commit

Permalink
6.0.0-rc.6
Browse files Browse the repository at this point in the history
- `App.getInitialProps` and `Page.getStaticProps` without wrapping
  • Loading branch information
kirill-konshin committed Apr 15, 2020
1 parent e67a35e commit 6dfdabb
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 177 deletions.
67 changes: 30 additions & 37 deletions README.md
Expand Up @@ -16,7 +16,7 @@ Contents:
- [Configuration](#configuration)
- [getStaticProps](#getstaticprops)
- [getServerSideProps](#getserversideprops)
- [getInitialProps](#getinitialprops)
- [Page.getInitialProps](#pagegetinitialprops)
- [App](#app)
- [App and getServerSideProps or getStaticProps at page level](#app-and-getserversideprops-or-getstaticprops-at-page-level)
- [How it works](#how-it-works)
Expand Down Expand Up @@ -176,7 +176,7 @@ export default wrapper.withRedux(connect(state: State) => state)(Page));

:warning: **Each time when pages that have `getServerSideProps` are opened by user the `HYDRATE` action will be dispatched.** The `payload` of this action will contain the `state` at the moment of server side rendering, it will not have client state, so your reducer must merge it with existing client state properly. More about this in [Server and Client State Separation](#server-and-client-state-separation).

## `getInitialProps`
## `Page.getInitialProps`

```typescript
import React, {Component} from 'react';
Expand All @@ -191,12 +191,10 @@ const Page: NextPage = () => {
);
};

Page.getInitialProps = wrapper.getInitialPageProps(
({store, pathname, req, res}) => {
console.log('2. Page.getInitialProps uses the store to dispatch things');
store.dispatch({type: 'TICK', payload: 'was set in error page ' + pathname});
}
);
Page.getInitialProps = ({store, pathname, req, res}) => {
console.log('2. Page.getInitialProps uses the store to dispatch things');
store.dispatch({type: 'TICK', payload: 'was set in error page ' + pathname});
};

export default wrapper.withRedux(Page);
```
Expand All @@ -208,12 +206,14 @@ Stateless function component also can be replaced with class:
```typescript
class Page extends Component {

public static getInitialProps = wrapper.getInitialPageProps(...)
public static getInitialProps = () => { ... };

render() {
// stuff
}
}

export default wrapper.withRedux(Page);
```

## App
Expand All @@ -226,26 +226,25 @@ The wrapper can also be attached to your `_app` component (located in `/pages`).
# pages/_app.tsx

import React from 'react';
import App, {AppInitialProps} from 'next/app';
import App, {AppInitialProps, AppContext} from 'next/app';
import {wrapper, State} from '../components/store';
import {State} from '../components/reducer';

class MyApp extends App<AppInitialProps> {

public static getInitialProps = wrapper.getInitialAppProps<Promise<AppInitialProps>>(
async ({Component, ctx}) => {
public static getInitialProps = async ({Component, ctx}: AppContext) => {

ctx.store.dispatch({type: 'TOE', payload: 'was set in _app'});
ctx.store.dispatch({type: 'TOE', payload: 'was set in _app'});

return {
pageProps: {
// Call page-level getInitialProps
...(Component.getInitialProps ? await Component.getInitialProps(ctx) : {}),
// Some custom thing for all pages
pathname: ctx.pathname,
},
};

return {
pageProps: {
// Call page-level getInitialProps
...(Component.getInitialProps ? await Component.getInitialProps(ctx) : {}),
// Some custom thing for all pages
pathname: ctx.pathname,
},
};
},
);

public render() {
Expand Down Expand Up @@ -503,12 +502,12 @@ Then in the `pages/_app` wait stop saga and wait for it to finish when execution
```typescript
import React from 'react';
import App, {AppInitialProps} from 'next/app';
import App, {AppInitialProps, AppContext} from 'next/app';
import {END} from 'redux-saga';
import {SagaStore, wrapper} from '../components/store';

class WrappedApp extends App<AppInitialProps> {
public static getInitialProps = wrapper.getInitialAppProps<Promise<AppInitialProps>>(async ({Component, ctx}) => {
public static getInitialProps = async ({Component, ctx}: AppContext) => {
// 1. Wait for all page actions to dispatch
const pageProps = {
...(Component.getInitialProps ? await Component.getInitialProps(ctx) : {}),
Expand All @@ -524,7 +523,7 @@ class WrappedApp extends App<AppInitialProps> {
return {
pageProps,
};
});
};

public render() {
const {Component, pageProps} = this.props;
Expand Down Expand Up @@ -671,23 +670,17 @@ export default connect(
Major change in the way how things are wrapped in version 6.
1. `withRedux` no longer takes `makeStore` and config as parameters, you need to create a wrapper `const wrapper = createWrapper(makeStore, {debug: true})` and then use `wrapper.withRedux(Page)`

2. `makeStore` no longer gets `initialState`, the signature is `makeStore(context: Context)`, where context could be [`NextPageContext`](https://nextjs.org/docs/api-reference/data-fetching/getInitialProps) or [`AppContext`](https://nextjs.org/docs/advanced-features/custom-app) or [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) or [`getServerSideProps`](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering) context depending on which lifecycle function you will wrap

3. Each time when pages that have `getStaticProps` or `getServerSideProps` are opened by user the `HYDRATE` action will be dispatched. 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

5. App's `getInitialProps` need to be wrapped with `wrapper.getInitialProps`, Page's `getInitialProps` can remain unwrapped then
1. Default export `withRedux` is marked deprecated, you should create a wrapper `const wrapper = createWrapper(makeStore, {debug: true})` and then use `wrapper.withRedux(Page)`.
6. If you haven't used App (early versions of this lib) then `getInitialProps` of Pages need to be wrapped with `wrapper.getInitialPageProps`
2. Your `makeStore` function no longer gets `initialState`, it only receives the context: `makeStore(context: Context)`. Context could be [`NextPageContext`](https://nextjs.org/docs/api-reference/data-fetching/getInitialProps) or [`AppContext`](https://nextjs.org/docs/advanced-features/custom-app) or [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) or [`getServerSideProps`](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering) context depending on which lifecycle function you will wrap. Instead, you need to handle the `HYDRATE` action in the reducer. 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.
7. `App` should no longer wrap it's childern with `Provider`
3. `App` should no longer wrap its children with `Provider`, it is now done internally.
8. `isServer` is no longer passed in context/props, use your own function or simple check `const isServer = typeof window === 'undefined'`
4. `isServer` is not passed in `context`/`props`, use your own function or simple check `const isServer = typeof window === 'undefined'` or `!!context.req` or `!!context.ctx.req`.
9. `WrappedAppProps` was renamed to `WrapperProps`
5. `store` is not passed to wrapped component props.
10. `store` is not passed to wrapped component props
6. `WrappedAppProps` was renamed to `WrapperProps`.
## Upgrade from 1.x to 2.x
Expand Down
5 changes: 3 additions & 2 deletions packages/demo-page/src/pages/_error.tsx
@@ -1,14 +1,15 @@
import React, {Component} from 'react';
import Link from 'next/link';
import {NextPageContext} from 'next';
import {connect} from 'react-redux';
import {wrapper} from '../components/store';
import {State} from '../components/reducer';

class ErrorPage extends Component<State> {
public static getInitialProps = wrapper.getInitialPageProps(({store, pathname}) => {
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});
});
};

render() {
const {page} = this.props;
Expand Down
4 changes: 1 addition & 3 deletions packages/demo-saga/src/components/store.tsx
@@ -1,5 +1,5 @@
import {createStore, applyMiddleware, Store} from 'redux';
import {MakeStore, createWrapper, Context} from 'next-redux-wrapper';
import {MakeStore, Context} from 'next-redux-wrapper';
import createSagaMiddleware, {Task} from 'redux-saga';
import reducer, {State} from './reducer';
import rootSaga from './saga';
Expand All @@ -21,5 +21,3 @@ export const makeStore: MakeStore<State> = (context: Context) => {
// 4: now return the store:
return store;
};

export const wrapper = createWrapper<State>(makeStore, {debug: true});
11 changes: 6 additions & 5 deletions packages/demo-saga/src/pages/_app.tsx
@@ -1,10 +1,11 @@
import React from 'react';
import App, {AppInitialProps} from 'next/app';
import App, {AppContext, AppInitialProps} from 'next/app';
import {END} from 'redux-saga';
import {SagaStore, wrapper} from '../components/store';
import {SagaStore, makeStore} from '../components/store';
import withRedux from 'next-redux-wrapper';

class WrappedApp extends App<AppInitialProps> {
public static getInitialProps = wrapper.getInitialAppProps<Promise<AppInitialProps>>(async ({Component, ctx}) => {
public static getInitialProps = async ({Component, ctx}: AppContext) => {
// 1. Wait for all page actions to dispatch
const pageProps = {
...(Component.getInitialProps ? await Component.getInitialProps(ctx) : {}),
Expand All @@ -21,12 +22,12 @@ class WrappedApp extends App<AppInitialProps> {
return {
pageProps,
};
});
};

public render() {
const {Component, pageProps} = this.props;
return <Component {...pageProps} />;
}
}

export default wrapper.withRedux(WrappedApp);
export default withRedux(makeStore)(WrappedApp);
6 changes: 3 additions & 3 deletions packages/demo/src/pages/_app.tsx
@@ -1,9 +1,9 @@
import React from 'react';
import App, {AppInitialProps} from 'next/app';
import App, {AppInitialProps, AppContext} from 'next/app';
import {wrapper} from '../components/store';

class WrappedApp extends App<AppInitialProps> {
public static getInitialProps = wrapper.getInitialAppProps<Promise<AppInitialProps>>(async ({Component, ctx}) => {
public static getInitialProps = async ({Component, ctx}: AppContext) => {
// Keep in mind that this will be called twice on server, one for page and second for error page
ctx.store.dispatch({type: 'APP', payload: 'was set in _app'});

Expand All @@ -15,7 +15,7 @@ class WrappedApp extends App<AppInitialProps> {
appProp: ctx.pathname,
},
};
});
};

public render() {
const {Component, pageProps} = this.props;
Expand Down
2 changes: 1 addition & 1 deletion packages/demo/src/pages/server.tsx
Expand Up @@ -27,7 +27,7 @@ const Server: NextPage<OtherProps> = ({appProp, getServerSideProp}) => {
);
};

export const getStaticProps = wrapper.getStaticProps(({store}) => {
export const getServerSideProps = wrapper.getServerSideProps(({store}) => {
store.dispatch({type: 'PAGE', payload: 'server'});
return {props: {getServerSideProp: 'bar'}};
});
Expand Down
32 changes: 20 additions & 12 deletions packages/wrapper/src/index.tsx
Expand Up @@ -111,22 +111,22 @@ export const createWrapper = <S extends {} = any, A extends Action = AnyAction>(
};

const getInitialPageProps = <P extends {} = any>(
callback?: (context: NextPageContext & {store: Store<S, A>}) => P | void,
callback: (context: NextPageContext & {store: Store<S, A>}) => P | void,
) => async (context: NextPageContext) => {
if (context.store) {
console.warn('No need to wrap pages if _app was wrapped');
return callback ? await callback(context as any) : undefined;
return callback(context as any);
}
return await makeProps({callback, context});
return makeProps({callback, context});
};

const getInitialAppProps = <P extends {} = any>(
callback?: (context: AppContext & {store: Store<S, A>}) => P | void,
callback: (context: AppContext & {store: Store<S, A>}) => P | void,
) => async (context: AppContext) =>
(await makeProps({callback, context, isApp: true})) as WrapperProps & AppInitialProps; // this is just to convince TS

const getStaticProps = <P extends {} = any>(
callback?: (context: GetStaticPropsContext & {store: Store<S, A>}) => P | void,
callback: (context: GetStaticPropsContext & {store: Store<S, A>}) => P | void,
): GetStaticProps<P> => async (context: any) => {
const {
initialProps: {props, ...settings},
Expand All @@ -143,7 +143,7 @@ export const createWrapper = <S extends {} = any, A extends Action = AnyAction>(
};

const getServerSideProps = <P extends {} = any>(
callback?: (context: GetServerSidePropsContext & {store: Store<S, A>}) => P | void,
callback: (context: GetServerSidePropsContext & {store: Store<S, A>}) => P | void,
): GetServerSideProps<P> => getStaticProps<P>(callback as any) as any; // just not to repeat myself

const withRedux = (Component: NextComponentType | App | any) => {
Expand Down Expand Up @@ -208,25 +208,33 @@ export const createWrapper = <S extends {} = any, A extends Action = AnyAction>(
}

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

return {
getInitialAppProps,
getInitialPageProps,
getServerSideProps,
getStaticProps,
withRedux,
};
};

// 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.',
);
return createWrapper(makeStore, config).withRedux;
};

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

export interface Config {
Expand Down
8 changes: 6 additions & 2 deletions packages/wrapper/tests/client.spec.tsx
Expand Up @@ -29,16 +29,20 @@ describe('client integration', () => {
});

test('API functions', async () => {
expect(await wrapper.getInitialPageProps()({} as any)).toEqual({
const Page = () => null;
Page.getInitialProps = () => null;
expect(await (wrapper.withRedux(Page) as any).getInitialProps({} as any)).toEqual({
initialProps: {},
initialState: defaultState,
});
});
});

test('store is available in window when created', async () => {
const Page = () => null;
Page.getInitialProps = () => null;
const wrapper = createWrapper(makeStore, {storeKey: 'testStoreKey'});
await wrapper.getInitialPageProps()({} as any);
await (wrapper.withRedux(Page) as any).getInitialProps({} as any);
expect(w.testStoreKey.getState()).toEqual(defaultState);
});
});

0 comments on commit 6dfdabb

Please sign in to comment.