Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for complex sagas asynchronously initializing the store state at server-side #438

Open
wants to merge 6 commits into
base: 7.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 36 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -916,27 +916,34 @@ Create your root saga as usual, then implement the store creator:

```typescript
import {createStore, applyMiddleware, Store} from 'redux';
import {createWrapper, Context} from 'next-redux-wrapper';
import {createInitSagaMonitor, createWrapper, InitSagaMonitor, Context} from 'next-redux-wrapper';
import createSagaMiddleware, {Task} from 'redux-saga';
import reducer, {State} from './reducer';
import rootSaga from './saga';

export interface SagaStore extends Store {
sagaTask?: Task;
export interface SagaStore extends Store<State> {
initMonitor: InitSagaMonitor;
sagaTask: Task;
}

export const makeStore = (context: Context) => {
const INITIALIZATION_TIMEOUT = 30_000; // 30 seconds

export const makeStore = (context: Context): SagaStore => {
// 1: Create the middleware
const sagaMiddleware = createSagaMiddleware();
const initMonitor = createInitSagaMonitor(INITIALIZATION_TIMEOUT);
const sagaMiddleware = createSagaMiddleware({sagaMonitor: initMonitor.monitor});

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

// 3: Run your sagas on server
(store as SagaStore).sagaTask = sagaMiddleware.run(rootSaga);
const sagaTask = sagaMiddleware.run(rootSaga);

// 4: now return the store:
return store;
// 4: Now return the store with access to init monitor and root saga task
return Object.assign(store, {
initMonitor,
sagaTask,
});
};

export const wrapper = createWrapper<Store<State>>(makeStore, {debug: true});
Expand All @@ -947,23 +954,29 @@ export const wrapper = createWrapper<Store<State>>(makeStore, {debug: true});

```js
import {createStore, applyMiddleware} from 'redux';
import {createWrapper} from 'next-redux-wrapper';
import {createInitSagaMonitor, createWrapper} from 'next-redux-wrapper';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer';
import rootSaga from './saga';

const INITIALIZATION_TIMEOUT = 30_000; // 30 seconds

export const makeStore = context => {
// 1: Create the middleware
const sagaMiddleware = createSagaMiddleware();
const initMonitor = createInitSagaMonitor(INITIALIZATION_TIMEOUT);
const sagaMiddleware = createSagaMiddleware({sagaMonitor: initMonitor.monitor});

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

// 3: Run your sagas on server
store.sagaTask = sagaMiddleware.run(rootSaga);
const sagaTask = sagaMiddleware.run(rootSaga);

// 4: now return the store:
return store;
// 4: Now return the store with access to init monitor and root saga task
return Object.assign(store, {
initMonitor,
sagaTask,
});
};

export const wrapper = createWrapper(makeStore, {debug: true});
Expand All @@ -989,10 +1002,13 @@ class WrappedApp extends App<AppInitialProps> {
...(await App.getInitialProps(context)).pageProps,
};

// 2. Stop the saga if on server
// 2. Stop the saga if on server once the initialization is done
if (context.ctx.req) {
const sagaStore = store as SagaStore;
sagaStore.initMonitor.start(); // Start the init monitor after sagas started to do their work
await sagaStore.initMonitor.initCompletion;
store.dispatch(END);
await (store as SagaStore).sagaTask.toPromise();
await sagaStore.sagaTask.toPromise();
}

// 3. Return props
Expand Down Expand Up @@ -1025,8 +1041,10 @@ class WrappedApp extends App {
...(await App.getInitialProps(context)).pageProps,
};

// 2. Stop the saga if on server
// 2. Stop the saga if on server once the initialization is done
if (context.ctx.req) {
store.initMonitor.start(); // Start the init monitor after sagas started to do their work
await store.initMonitor.initCompletion;
store.dispatch(END);
await store.sagaTask.toPromise();
}
Expand Down Expand Up @@ -1054,6 +1072,8 @@ In order to use it with `getServerSideProps` or `getStaticProps` you need to `aw
export const getServerSideProps = ReduxWrapper.getServerSideProps(async ({store, req, res, ...etc}) => {
// regular stuff
store.dispatch(ApplicationSlice.actions.updateConfiguration());
store.initMonitor.start(); // Start the init monitor after sagas started to do their work
await store.initMonitor.initCompletion;
// end the saga
store.dispatch(END);
await store.sagaTask.toPromise();
Expand Down
19 changes: 13 additions & 6 deletions packages/demo-saga-page/src/components/store.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import {createStore, applyMiddleware, Store} from 'redux';
import logger from 'redux-logger';
import createSagaMiddleware, {Task} from 'redux-saga';
import {Context, createWrapper} from 'next-redux-wrapper';
import {Context, createInitSagaMonitor, createWrapper, InitSagaMonitor} from 'next-redux-wrapper';
import reducer, {State} from './reducer';
import rootSaga from './saga';

export interface SagaStore extends Store<State> {
initMonitor: InitSagaMonitor;
sagaTask: Task;
}

const makeStore = (context: Context) => {
const INITIALIZATION_TIMEOUT = 30_000; // 30 seconds

const makeStore = (context: Context): SagaStore => {
// 1: Create the middleware
const sagaMiddleware = createSagaMiddleware();
const initMonitor = createInitSagaMonitor(INITIALIZATION_TIMEOUT);
const sagaMiddleware = createSagaMiddleware({sagaMonitor: initMonitor.monitor});

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

// 3: Run your sagas on server
(store as SagaStore).sagaTask = sagaMiddleware.run(rootSaga);
const sagaTask = sagaMiddleware.run(rootSaga);

// 4: now return the store:
return store;
// 4: Now return the store with access to init monitor and root saga task
return Object.assign(store, {
initMonitor,
sagaTask,
});
};

export const wrapper = createWrapper<SagaStore>(makeStore as any);
9 changes: 6 additions & 3 deletions packages/demo-saga-page/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ const Page: NextPage<ConnectedPageProps> = ({custom}: ConnectedPageProps) => {
};

export const getServerSideProps = wrapper.getServerSideProps(store => async () => {
store.dispatch({type: SAGA_ACTION});
store.dispatch(END);
await (store as SagaStore).sagaTask.toPromise();
const sagaStore = store as SagaStore;
sagaStore.dispatch({type: SAGA_ACTION});
sagaStore.initMonitor.start(); // Start the init monitor after sagas started to do their work
await sagaStore.initMonitor.initCompletion;
sagaStore.dispatch(END);
await sagaStore.sagaTask.toPromise();

return {props: {custom: 'custom'}};
});
Expand Down
17 changes: 12 additions & 5 deletions packages/demo-saga/src/components/store.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import {createStore, applyMiddleware, Store} from 'redux';
import logger from 'redux-logger';
import createSagaMiddleware, {Task} from 'redux-saga';
import {Context, createWrapper} from 'next-redux-wrapper';
import {Context, createInitSagaMonitor, createWrapper, InitSagaMonitor} from 'next-redux-wrapper';
import reducer from './reducer';
import rootSaga from './saga';

export interface SagaStore extends Store {
initMonitor: InitSagaMonitor;
sagaTask: Task;
}

const INITIALIZATION_TIMEOUT = 30_000; // 30 seconds

export const makeStore = (context: Context) => {
// 1: Create the middleware
const sagaMiddleware = createSagaMiddleware();
const initMonitor = createInitSagaMonitor(INITIALIZATION_TIMEOUT);
const sagaMiddleware = createSagaMiddleware({sagaMonitor: initMonitor.monitor});

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

// 3: Run your sagas on server
(store as SagaStore).sagaTask = sagaMiddleware.run(rootSaga);
const sagaTask = sagaMiddleware.run(rootSaga);

// 4: now return the store:
return store;
// 4: Now return the store with access to init monitor and root saga task
return Object.assign(store, {
initMonitor,
sagaTask,
});
};

export const wrapper = createWrapper<SagaStore>(makeStore as any);
9 changes: 6 additions & 3 deletions packages/demo-saga/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ class MyApp extends React.Component<AppProps> {
...(await App.getInitialProps(context)).pageProps,
};

// 2. Stop the saga if on server
// 2. Stop the saga if on server once the initialization is done
if (context.ctx.req) {
store.dispatch(END);
await (store as SagaStore).sagaTask.toPromise();
const sagaStore = store as SagaStore;
sagaStore.initMonitor.start(); // Start the init monitor after sagas started to do their work
await sagaStore.initMonitor.initCompletion;
sagaStore.dispatch(END);
await sagaStore.sagaTask.toPromise();
}

// 3. Return props
Expand Down
2 changes: 2 additions & 0 deletions packages/wrapper/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
NextPageContext,
} from 'next';

export * from './initSagaMonitorFactory';

/**
* Quick note on Next.js return types:
*
Expand Down
Loading