├── .all-contributorsrc ├── .editorconfig ├── .gitignore ├── .prettierignore ├── .travis.yml ├── .yarnrc ├── LICENSE ├── README.md ├── babel.config.js ├── components └── layout.js ├── index.js ├── jest.setup.js ├── package.json ├── pages ├── _app.js ├── async.js ├── index.js └── sync.js ├── rollup.config.js ├── test ├── __snapshots__ │ ├── client.test.js.snap │ └── server.test.js.snap ├── client.test.js ├── components │ ├── async-get-initial-props.js │ ├── class-component.js │ ├── functional-component.js │ └── sync-get-initial-props.js ├── constants.js ├── server.test.js ├── store │ ├── root-reducer.js │ ├── root-saga.js │ └── store-wrapper.js └── utils │ ├── create-snapshot.js │ ├── get-initial-props.js │ └── get-server-context.js └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "next-redux-saga", 3 | "projectOwner": "bmealhouse", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": false, 11 | "contributors": [ 12 | { 13 | "login": "bmealhouse", 14 | "name": "Brent Mealhouse", 15 | "avatar_url": "https://avatars3.githubusercontent.com/u/3741255?v=4", 16 | "profile": "https://twitter.com/bmealhouse", 17 | "contributions": [ 18 | "code", 19 | "test", 20 | "doc", 21 | "maintenance", 22 | "question" 23 | ] 24 | }, 25 | { 26 | "login": "bbortt", 27 | "name": "Timon Borter", 28 | "avatar_url": "https://avatars0.githubusercontent.com/u/12272901?v=4", 29 | "profile": "https://bbortt.github.io", 30 | "contributions": [ 31 | "code", 32 | "test", 33 | "doc", 34 | "maintenance", 35 | "question" 36 | ] 37 | }, 38 | { 39 | "login": "JerryCauser", 40 | "name": "Artem Abzanov", 41 | "avatar_url": "https://avatars3.githubusercontent.com/u/5141037?v=4", 42 | "profile": "https://abzanov.com", 43 | "contributions": [ 44 | "code", 45 | "test", 46 | "doc" 47 | ] 48 | }, 49 | { 50 | "login": "RobbinHabermehl", 51 | "name": "Robbin Habermehl", 52 | "avatar_url": "https://avatars1.githubusercontent.com/u/1640272?v=4", 53 | "profile": "https://github.com/RobbinHabermehl", 54 | "contributions": [ 55 | "code", 56 | "test", 57 | "doc" 58 | ] 59 | } 60 | ], 61 | "contributorsPerLine": 7 62 | } 63 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # build 4 | /.next 5 | /dist 6 | 7 | # dependencies 8 | /node_modules 9 | 10 | # editors 11 | /.idea 12 | 13 | # testing 14 | /coverage 15 | 16 | # misc 17 | .DS_Store 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # See https://prettier.io/docs/en/ignore.html for more about ignoring files. 2 | 3 | # build 4 | /.next 5 | /dist 6 | 7 | # test 8 | /coverage 9 | 10 | # misc 11 | package.json 12 | README.md 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | - "12" 6 | - "13" 7 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | save-prefix "" 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Brent Mealhouse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | next-redux-saga 2 | ===== 3 | 4 | # This project is no longer maintained! 5 | 6 | Because `next.js` has grown massively and other packages with better support have covered the `redux-saga` SSR functionality. 7 | See [#79](https://github.com/bmealhouse/next-redux-saga/issues/79) for more information. 8 | 9 | [![npm version](https://img.shields.io/npm/v/next-redux-saga.svg)](https://npmjs.org/package/next-redux-saga) 10 | [![npm downloads](https://img.shields.io/npm/dm/next-redux-saga.svg)](https://npmjs.org/package/next-redux-saga) 11 | [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) 12 | [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 13 | [![Build Status](https://travis-ci.com/bmealhouse/next-redux-saga.svg?branch=master)](https://travis-ci.com/bmealhouse/next-redux-saga) 14 | [![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg)](#contributors) 15 | 16 | > `redux-saga` HOC for [Next.js](https://github.com/zeit/next.js/). controlled `redux-saga` execution for server side rendering. 17 | 18 | > **Attention:** Synchronous HOC is no longer supported since version 4.0.0! 19 | 20 | ## Installation 21 | 22 | ```sh 23 | yarn add next-redux-saga 24 | ``` 25 | 26 | ## Getting Started 27 | 28 | Check out the official [Next.js example](https://github.com/zeit/next.js/tree/canary/examples/with-redux-saga) or clone this repository and run the local example. 29 | 30 | ### Try the local example 31 | 32 | 1. Clone this repository 33 | 1. Install dependencies: `yarn` 34 | 1. Start the project: `yarn start` 35 | 1. Open [http://localhost:3000](http://localhost:3000) 36 | 37 | ## Usage 38 | 39 | `next-redux-saga` uses the redux store created by [next-redux-wrapper](https://github.com/kirill-konshin/next-redux-wrapper). Please refer to their documentation for more information. 40 | 41 | ### Configure the Store wrapper 42 | 43 | ```js 44 | import {applyMiddleware, createStore} from 'redux' 45 | import createSagaMiddleware from 'redux-saga' 46 | import {createWrapper} from 'next-redux-wrapper' 47 | 48 | import rootReducer from './root-reducer' 49 | import rootSaga from './root-saga' 50 | 51 | const makeStore = context => { 52 | const sagaMiddleware = createSagaMiddleware() 53 | const store = createStore( 54 | rootReducer, 55 | applyMiddleware(sagaMiddleware), 56 | ) 57 | 58 | store.sagaTask = sagaMiddleware.run(rootSaga) 59 | 60 | return store 61 | } 62 | 63 | const wrapper = createWrapper(makeStore) 64 | 65 | export default wrapper 66 | 67 | ``` 68 | 69 | ### Configure Custom `_app.js` Component 70 | 71 | ```js 72 | import React from 'react' 73 | import App from 'next/app' 74 | import withReduxSaga from 'next-redux-saga' 75 | 76 | import wrapper from './store-wrapper' 77 | 78 | class ExampleApp extends App { 79 | static async getInitialProps({Component, ctx}) { 80 | let pageProps = {} 81 | 82 | if (Component.getInitialProps) { 83 | pageProps = await Component.getInitialProps(ctx) 84 | } 85 | 86 | return {pageProps} 87 | } 88 | 89 | render() { 90 | const {Component, pageProps} = this.props 91 | return ( 92 | 93 | ) 94 | } 95 | } 96 | 97 | export default wrapper.withRedux(withReduxSaga(ExampleApp)) 98 | ``` 99 | 100 | ### Connect Page Components 101 | 102 | ```js 103 | import React, {Component} from 'react' 104 | import {connect} from 'react-redux' 105 | 106 | class ExamplePage extends Component { 107 | static async getInitialProps({store}) { 108 | store.dispatch({type: 'SOME_ASYNC_ACTION_REQUEST'}) 109 | return {staticData: 'Hello world!'} 110 | } 111 | 112 | render() { 113 | return
{this.props.staticData}
114 | } 115 | } 116 | 117 | export default connect(state => state)(ExamplePage) 118 | ``` 119 | 120 | ## Contributors 121 | 122 | 123 | 124 |
Brent Mealhouse
Brent Mealhouse

💻 ⚠️ 📖 🚧 💬
Timon Borter
Timon Borter

💻 ⚠️ 📖 🚧 💬
Artem Abzanov
Artem Abzanov

💻 ⚠️ 📖
Robbin Habermehl
Robbin Habermehl

💻 ⚠️ 📖
125 | 126 | 127 | ## Contributing 128 | 129 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device 130 | 1. Install the dependecies: `yarn` 131 | 1. Link the package to the global module directory: `yarn link` 132 | 1. Run `yarn test --watch` and start making your changes 133 | 1. You can use `yarn link next-redux-saga` to test your changes in an actual project 134 | 135 | ## LICENSE 136 | 137 | This project is licensed under the terms of MIT license. See the [license file](https://github.com/bmealhouse/next-redux-saga/blob/master/LICENSE) for more information. 138 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'presets': [ 3 | 'next/babel', 4 | ], 5 | 'env': { 6 | 'test': { 7 | 'presets': [ 8 | [ 9 | 'next/babel', 10 | { 11 | 'preset-env': { 12 | 'modules': 'commonjs', 13 | }, 14 | }, 15 | ], 16 | ], 17 | }, 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /components/layout.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | 3 | import {node} from 'prop-types' 4 | 5 | import Router from 'next/router' 6 | 7 | Router.onRouteChangeComplete = () => { 8 | document.querySelectorAll('.spinner').forEach(el => { 9 | el.classList.remove('spinner') 10 | }) 11 | } 12 | 13 | class App extends Component { 14 | static propTypes = { 15 | children: node, 16 | } 17 | 18 | componentDidMount() { 19 | if (typeof window === 'undefined') return 20 | Router.prefetch('/sync') 21 | Router.prefetch('/async') 22 | } 23 | 24 | handleClick = target => { 25 | Router.push(target) 26 | } 27 | 28 | render() { 29 | return ( 30 |
31 | this.handleClick('/')}> 32 | Home 33 | 34 | this.handleClick('/sync')}> 35 | Sync 36 | 37 | this.handleClick('/async')}> 38 | Async 39 | 40 | {this.props.children} 41 | 76 | 110 |
111 | ) 112 | } 113 | } 114 | 115 | export default App 116 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React, {Component} from 'react' 3 | import {END} from 'redux-saga' 4 | 5 | function withReduxSaga(BaseComponent) { 6 | class WrappedComponent extends Component { 7 | static displayName = `withReduxSaga(${BaseComponent.displayName || 8 | BaseComponent.name || 9 | 'BaseComponent'})` 10 | 11 | static async getInitialProps(props) { 12 | const {store} = props.ctx 13 | 14 | let pageProps = {} 15 | if (BaseComponent.getInitialProps) { 16 | pageProps = await BaseComponent.getInitialProps(props) 17 | } 18 | 19 | // Stop saga on the server 20 | if (typeof window === 'undefined') { 21 | store.dispatch(END) 22 | await store.sagaTask.toPromise() 23 | } 24 | 25 | return pageProps 26 | } 27 | 28 | render() { 29 | return 30 | } 31 | } 32 | 33 | return WrappedComponent 34 | } 35 | 36 | export default withReduxSaga 37 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | import {configure} from 'enzyme' 2 | import Adapter from 'enzyme-adapter-react-16' 3 | 4 | configure({adapter: new Adapter()}) 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-redux-saga", 3 | "version": "5.0.0-0", 4 | "description": "redux-saga HOC for Next.js", 5 | "repository": "https://github.com/bmealhouse/next-redux-saga.git", 6 | "author": "Brent Mealhouse ", 7 | "contributors": [ 8 | "Timon Borter", 9 | "Artem Abzanov", 10 | "Robbin Habermehl" 11 | ], 12 | "license": "MIT", 13 | "main": "dist/next-redux-saga.umd.js", 14 | "jsnext:main": "dist/next-redux-saga.es.js", 15 | "module": "dist/next-redux-saga.es.js", 16 | "files": [ 17 | "dist" 18 | ], 19 | "scripts": { 20 | "build": "rollup --config", 21 | "format": "prettier --write", 22 | "prerelease": "yarn build", 23 | "release": "release-it", 24 | "start": "next", 25 | "test": "jest" 26 | }, 27 | "jest": { 28 | "collectCoverage": true, 29 | "verbose": true, 30 | "setupFiles": [ 31 | "/jest.setup.js" 32 | ], 33 | "coveragePathIgnorePatterns": [ 34 | "/node_modules/", 35 | "package.json", 36 | "yarn.lock" 37 | ], 38 | "transformIgnorePatterns": [ 39 | "node_modules/(?!(@babel/runtime)/)" 40 | ] 41 | }, 42 | "lint-staged": { 43 | "*.js": [ 44 | "prettier --write", 45 | "git add" 46 | ] 47 | }, 48 | "prettier": { 49 | "useTabs": false, 50 | "semi": false, 51 | "singleQuote": true, 52 | "trailingComma": "all", 53 | "bracketSpacing": false 54 | }, 55 | "renovate": { 56 | "automerge": true, 57 | "automergeType": "branch-push", 58 | "pinVersions": true, 59 | "schedule": [ 60 | "every friday" 61 | ], 62 | "packageRules": [ 63 | { 64 | "packageNames": [ 65 | "jest", 66 | "babel-jest" 67 | ], 68 | "groupName": "jest packages" 69 | } 70 | ] 71 | }, 72 | "xo": { 73 | "envs": [ 74 | "browser", 75 | "jest" 76 | ], 77 | "extends": [ 78 | "plugin:react/recommended", 79 | "prettier/react" 80 | ], 81 | "parser": "babel-eslint", 82 | "prettier": true, 83 | "rules": { 84 | "capitalized-comments": 0, 85 | "import/order": 0 86 | }, 87 | "settings": { 88 | "react": { 89 | "version": "detect" 90 | } 91 | } 92 | }, 93 | "peerDependencies": { 94 | "redux-saga": "1.x" 95 | }, 96 | "dependencies": { 97 | "@babel/runtime": "7.3.1" 98 | }, 99 | "devDependencies": { 100 | "@babel/core": "7.10.2", 101 | "@rollup/plugin-babel": "5.0.3", 102 | "babel-eslint": "10.1.0", 103 | "babel-jest": "26.0.1", 104 | "enzyme": "3.11.0", 105 | "enzyme-adapter-react-16": "1.15.2", 106 | "enzyme-to-json": "3.5.0", 107 | "eslint": "7.2.0", 108 | "eslint-plugin-prettier": "3.1.4", 109 | "eslint-plugin-react": "7.20.0", 110 | "husky": "4.2.5", 111 | "jest": "26.0.1", 112 | "jest-express": "1.12.0", 113 | "lint-staged": "10.2.10", 114 | "next": "9.4.4", 115 | "next-redux-wrapper": "6.0.2", 116 | "prettier": "2.0.5", 117 | "prop-types": "15.7.2", 118 | "react": "16.13.1", 119 | "react-dom": "16.13.1", 120 | "react-redux": "7.2.0", 121 | "react-test-renderer": "16.13.1", 122 | "redux": "4.0.5", 123 | "redux-saga": "1.1.3", 124 | "release-it": "13.6.3", 125 | "rollup": "2.16.1", 126 | "webpack": "4.43.0", 127 | "xo": "0.32.0" 128 | }, 129 | "bugs": { 130 | "url": "https://github.com/bmealhouse/next-redux-saga/issues" 131 | }, 132 | "homepage": "https://github.com/bmealhouse/next-redux-saga#readme", 133 | "keywords": [ 134 | "next", 135 | "nextjs", 136 | "Next.js", 137 | "next-redux", 138 | "next-redux-wrapper", 139 | "react", 140 | "react-redux", 141 | "redux", 142 | "redux-saga" 143 | ] 144 | } 145 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import App from 'next/app' 4 | 5 | import wrapper from '../test/store/store-wrapper' 6 | 7 | class ExampleApp extends App { 8 | static async getInitialProps({Component, ctx}) { 9 | let pageProps = {} 10 | 11 | if (Component.getInitialProps) { 12 | pageProps = await Component.getInitialProps({ctx}) 13 | } 14 | 15 | return {pageProps} 16 | } 17 | 18 | render() { 19 | const {Component, pageProps} = this.props 20 | 21 | return ( 22 | 23 | ) 24 | } 25 | } 26 | 27 | export default wrapper.withRedux(ExampleApp) 28 | -------------------------------------------------------------------------------- /pages/async.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | 3 | import {string} from 'prop-types' 4 | 5 | import {connect} from 'react-redux' 6 | 7 | import withReduxSaga from '..' 8 | 9 | import Layout from '../components/layout' 10 | 11 | import { 12 | GET_SYNC_REDUX_PROP_TYPE, 13 | GET_ASYNC_REDUX_SAGA_PROP_TYPE, 14 | STATIC_PROP_TEXT, 15 | SYNC_REDUX_PROP_TEXT, 16 | } from '../test/constants' 17 | 18 | class AsyncExample extends Component { 19 | static propTypes = { 20 | staticProp: string, 21 | syncReduxProp: string, 22 | asyncReduxSagaProp: string, 23 | } 24 | 25 | static getInitialProps({ctx: {store}}) { 26 | store.dispatch({ 27 | type: GET_SYNC_REDUX_PROP_TYPE, 28 | data: SYNC_REDUX_PROP_TEXT, 29 | }) 30 | 31 | store.dispatch({type: GET_ASYNC_REDUX_SAGA_PROP_TYPE}) 32 | return {staticProp: STATIC_PROP_TEXT} 33 | } 34 | 35 | render() { 36 | const {staticProp, syncReduxProp, asyncReduxSagaProp} = this.props 37 | 38 | return ( 39 | 40 |
41 | Received static prop: 42 |
43 |             {staticProp}
44 |           
45 |
46 |
47 | Received synchronous Redux prop: 48 |
49 |             {syncReduxProp}
50 |           
51 |
52 |
53 | Received asynchronous Redux-Saga prop: 54 |
55 |             {asyncReduxSagaProp || 'loading...'}
56 |           
57 |
58 |
59 | ) 60 | } 61 | } 62 | 63 | export default withReduxSaga(connect(state => state)(AsyncExample)) 64 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Layout from '../components/layout' 4 | 5 | function Home() { 6 | return 7 | } 8 | 9 | export default Home 10 | -------------------------------------------------------------------------------- /pages/sync.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | 3 | import {string} from 'prop-types' 4 | 5 | import {connect} from 'react-redux' 6 | 7 | import withReduxSaga from '..' 8 | 9 | import Layout from '../components/layout' 10 | 11 | import { 12 | GET_SYNC_REDUX_PROP_TYPE, 13 | GET_ASYNC_REDUX_SAGA_PROP_TYPE, 14 | STATIC_PROP_TEXT, 15 | SYNC_REDUX_PROP_TEXT, 16 | } from '../test/constants' 17 | 18 | class SyncExample extends Component { 19 | static propTypes = { 20 | staticProp: string, 21 | syncReduxProp: string, 22 | asyncReduxSagaProp: string, 23 | } 24 | 25 | static getInitialProps({ctx: {store}}) { 26 | store.dispatch({ 27 | type: GET_SYNC_REDUX_PROP_TYPE, 28 | data: SYNC_REDUX_PROP_TEXT, 29 | }) 30 | 31 | store.dispatch({type: GET_ASYNC_REDUX_SAGA_PROP_TYPE}) 32 | return {staticProp: STATIC_PROP_TEXT} 33 | } 34 | 35 | render() { 36 | const {staticProp, syncReduxProp, asyncReduxSagaProp} = this.props 37 | 38 | return ( 39 | 40 |
41 | Received static prop: 42 |
43 |             {staticProp}
44 |           
45 |
46 |
47 | Received synchronous Redux prop: 48 |
49 |             {syncReduxProp}
50 |           
51 |
52 |
53 | Received asynchronous Redux-Saga prop: 54 |
55 |             {asyncReduxSagaProp || 'loading...'}
56 |           
57 |
58 |
59 | ) 60 | } 61 | } 62 | 63 | export default withReduxSaga(connect(state => state)(SyncExample)) 64 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel' 2 | import pkg from './package.json' // eslint-disable-line import/extensions 3 | 4 | export default ['umd', 'es'].map(format => ({ 5 | input: 'index.js', 6 | output: { 7 | file: `dist/${pkg.name}.${format}.js`, 8 | name: format === 'umd' ? pkg.name : undefined, 9 | format, 10 | sourcemap: true, 11 | globals: { 12 | react: 'React', 13 | 'redux-saga': 'ReduxSaga', 14 | '@abel/runtime/regenerator': 'regeneratorRuntime', 15 | }, 16 | }, 17 | plugins: [ 18 | babel({ 19 | exclude: 'node_modules/**', 20 | babelHelpers: 'runtime', 21 | }), 22 | ], 23 | external: ['react', 'redux-saga', '@babel/runtime/regenerator'], 24 | })) 25 | -------------------------------------------------------------------------------- /test/__snapshots__/client.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Wrapped component awaits asynchronous getInitialProps 1`] = ` 4 | 87 | 90 | 91 | `; 92 | 93 | exports[`Wrapped component awaits synchronous getInitialProps 1`] = ` 94 | 177 | 180 | 181 | `; 182 | 183 | exports[`Wrapped component passes along React props 1`] = ` 184 | 267 | 268 | 269 | `; 270 | 271 | exports[`Wrapped component skips getInitialProps when it does not exist 1`] = ` 272 | 355 | 356 | 357 | `; 358 | -------------------------------------------------------------------------------- /test/__snapshots__/server.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Wrapped component awaits asynchronous getInitialProps 1`] = ` 4 | 87 | 90 | 91 | `; 92 | 93 | exports[`Wrapped component awaits synchronous getInitialProps 1`] = ` 94 | 177 | 180 | 181 | `; 182 | 183 | exports[`Wrapped component passes along React props 1`] = ` 184 | 267 | 268 | 269 | `; 270 | 271 | exports[`Wrapped component skips getInitialProps when it does not exist 1`] = ` 272 | 355 | 356 | 357 | `; 358 | -------------------------------------------------------------------------------- /test/client.test.js: -------------------------------------------------------------------------------- 1 | import withReduxSaga from '..' 2 | 3 | import ClassComponent from './components/class-component' 4 | import FunctionalComponent from './components/functional-component' 5 | import AsyncGetInitialProps from './components/async-get-initial-props' 6 | import SyncGetInitialProps from './components/sync-get-initial-props' 7 | 8 | import wrapper from './store/store-wrapper' 9 | import createSnapshot from './utils/create-snapshot' 10 | import getInitialProps from './utils/get-initial-props' 11 | 12 | import {STATIC_PROP_TEXT, SYNC_REDUX_PROP_TEXT} from './constants' 13 | 14 | test('Wrapped component passes along React props', () => { 15 | const WrappedComponent = wrapper.withRedux( 16 | withReduxSaga(FunctionalComponent), 17 | ) 18 | 19 | createSnapshot(WrappedComponent) 20 | }) 21 | 22 | test('Wrapped component skips getInitialProps when it does not exist', async () => { 23 | const WrappedComponent = wrapper.withRedux( 24 | withReduxSaga(ClassComponent), 25 | ) 26 | 27 | const props = await getInitialProps(WrappedComponent) 28 | 29 | createSnapshot(WrappedComponent, props) 30 | }) 31 | 32 | test('Wrapped component awaits synchronous getInitialProps', async () => { 33 | const WrappedComponent = wrapper.withRedux( 34 | withReduxSaga(SyncGetInitialProps), 35 | ) 36 | 37 | const props = await getInitialProps(WrappedComponent) 38 | 39 | expect(props.initialState).toEqual({syncReduxProp: SYNC_REDUX_PROP_TEXT}) 40 | expect(props.initialProps).toEqual({staticProp: STATIC_PROP_TEXT}) 41 | 42 | createSnapshot(WrappedComponent, props) 43 | }) 44 | 45 | test('Wrapped component awaits asynchronous getInitialProps', async () => { 46 | const WrappedComponent = wrapper.withRedux( 47 | withReduxSaga(AsyncGetInitialProps), 48 | ) 49 | 50 | const props = await getInitialProps(WrappedComponent) 51 | 52 | expect(props.initialState).toEqual({syncReduxProp: SYNC_REDUX_PROP_TEXT}) 53 | expect(props.initialProps).toEqual({staticProp: STATIC_PROP_TEXT}) 54 | 55 | createSnapshot(WrappedComponent, props) 56 | }) 57 | -------------------------------------------------------------------------------- /test/components/async-get-initial-props.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React, {Component} from 'react' 3 | import {connect} from 'react-redux' 4 | 5 | import { 6 | GET_ASYNC_REDUX_SAGA_PROP_TYPE, 7 | GET_SYNC_REDUX_PROP_TYPE, 8 | STATIC_PROP_TEXT, 9 | SYNC_REDUX_PROP_TEXT, 10 | } from '../constants' 11 | 12 | class AsyncGetInitialProps extends Component { 13 | static async getInitialProps(props) { 14 | const {store} = props.ctx 15 | 16 | store.dispatch({ 17 | type: GET_SYNC_REDUX_PROP_TYPE, 18 | data: SYNC_REDUX_PROP_TEXT, 19 | }) 20 | 21 | store.dispatch({type: GET_ASYNC_REDUX_SAGA_PROP_TYPE}) 22 | return {staticProp: STATIC_PROP_TEXT} 23 | } 24 | 25 | render() { 26 | return
AsyncGetInitialProps({JSON.stringify(this.props)})
27 | } 28 | } 29 | 30 | export default connect(state => state)(AsyncGetInitialProps) 31 | -------------------------------------------------------------------------------- /test/components/class-component.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React, {Component} from 'react' 3 | import {connect} from 'react-redux' 4 | 5 | class ClassComponent extends Component { 6 | render() { 7 | const {mode} = this.props 8 | return ( 9 |
10 | ClassComponent( 11 | {JSON.stringify({mode})}) 12 |
13 | ) 14 | } 15 | } 16 | 17 | export default connect(state => state)(ClassComponent) 18 | -------------------------------------------------------------------------------- /test/components/functional-component.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | 4 | function FunctionalComponent(props) { 5 | return
FunctionalComponent({JSON.stringify(props)})
6 | } 7 | 8 | export default connect(state => state)(FunctionalComponent) 9 | -------------------------------------------------------------------------------- /test/components/sync-get-initial-props.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React, {Component} from 'react' 3 | import {connect} from 'react-redux' 4 | 5 | import { 6 | GET_SYNC_REDUX_PROP_TYPE, 7 | STATIC_PROP_TEXT, 8 | SYNC_REDUX_PROP_TEXT, 9 | } from '../constants' 10 | 11 | class SyncGetInitialProps extends Component { 12 | static getInitialProps(props) { 13 | const {store} = props.ctx 14 | 15 | store.dispatch({ 16 | type: GET_SYNC_REDUX_PROP_TYPE, 17 | data: SYNC_REDUX_PROP_TEXT, 18 | }) 19 | 20 | return {staticProp: STATIC_PROP_TEXT} 21 | } 22 | 23 | render() { 24 | return
SyncGetInitialProps({JSON.stringify(this.props)})
25 | } 26 | } 27 | 28 | export default connect(state => state)(SyncGetInitialProps) 29 | -------------------------------------------------------------------------------- /test/constants.js: -------------------------------------------------------------------------------- 1 | export const GET_SYNC_REDUX_PROP_TYPE = 'GET_SYNC_REDUX_PROP' 2 | 3 | export const GET_ASYNC_REDUX_SAGA_PROP_TYPE = 'GET_ASYNC_REDUX_SAGA_PROP' 4 | export const GET_ASYNC_REDUX_SAGA_PROP_TYPE_SUCCESS = 5 | 'GET_ASYNC_REDUX_SAGA_PROP_SUCCESS' 6 | 7 | export const STATIC_PROP_TEXT = 'Static message from getInitialProps()' 8 | export const SYNC_REDUX_PROP_TEXT = 'Synchronous message from Redux' 9 | export const ASYNC_REDUX_SAGA_PROP_TEXT = 'Asynchronous message from Redux-Saga' 10 | -------------------------------------------------------------------------------- /test/server.test.js: -------------------------------------------------------------------------------- 1 | /** @jest-environment node */ 2 | import withReduxSaga from '..' 3 | 4 | import AsyncGetInitialProps from './components/async-get-initial-props' 5 | import ClassComponent from './components/class-component' 6 | import FunctionalComponent from './components/functional-component' 7 | import SyncGetInitialProps from './components/sync-get-initial-props' 8 | 9 | import wrapper from './store/store-wrapper' 10 | import createSnapshot from './utils/create-snapshot' 11 | import getInitialProps from './utils/get-initial-props' 12 | 13 | import {ASYNC_REDUX_SAGA_PROP_TEXT, STATIC_PROP_TEXT, SYNC_REDUX_PROP_TEXT} from './constants' 14 | 15 | test('Wrapped component passes along React props', () => { 16 | const WrappedComponent = wrapper.withRedux( 17 | withReduxSaga(FunctionalComponent), 18 | ) 19 | 20 | createSnapshot(WrappedComponent) 21 | }) 22 | 23 | test('Wrapped component skips getInitialProps when it does not exist', async () => { 24 | const WrappedComponent = wrapper.withRedux( 25 | withReduxSaga(ClassComponent), 26 | ) 27 | 28 | const props = await getInitialProps(WrappedComponent) 29 | 30 | createSnapshot(WrappedComponent, props) 31 | }) 32 | 33 | test('Wrapped component awaits synchronous getInitialProps', async () => { 34 | const WrappedComponent = wrapper.withRedux( 35 | withReduxSaga(SyncGetInitialProps), 36 | ) 37 | 38 | const props = await getInitialProps(WrappedComponent) 39 | 40 | expect(props.initialState).toEqual({syncReduxProp: SYNC_REDUX_PROP_TEXT}) 41 | expect(props.initialProps).toEqual({staticProp: STATIC_PROP_TEXT}) 42 | 43 | createSnapshot(WrappedComponent, props) 44 | }) 45 | 46 | test('Wrapped component awaits asynchronous getInitialProps', async () => { 47 | const WrappedComponent = wrapper.withRedux( 48 | withReduxSaga(AsyncGetInitialProps), 49 | ) 50 | 51 | const props = await getInitialProps(WrappedComponent) 52 | 53 | console.log('props: ', props) 54 | 55 | expect(props.initialState).toEqual({ 56 | syncReduxProp: SYNC_REDUX_PROP_TEXT, 57 | asyncReduxSagaProp: ASYNC_REDUX_SAGA_PROP_TEXT, 58 | }) 59 | expect(props.initialProps).toEqual({staticProp: STATIC_PROP_TEXT}) 60 | 61 | createSnapshot(WrappedComponent, props) 62 | }) 63 | -------------------------------------------------------------------------------- /test/store/root-reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_SYNC_REDUX_PROP_TYPE, 3 | GET_ASYNC_REDUX_SAGA_PROP_TYPE_SUCCESS, 4 | } from '../constants' 5 | 6 | const initialState = {} 7 | 8 | function rootReducer(state = initialState, action) { 9 | switch (action.type) { 10 | case GET_SYNC_REDUX_PROP_TYPE: 11 | return {...state, syncReduxProp: action.data} 12 | case GET_ASYNC_REDUX_SAGA_PROP_TYPE_SUCCESS: 13 | return {...state, asyncReduxSagaProp: action.data} 14 | default: 15 | return state 16 | } 17 | } 18 | 19 | export default rootReducer 20 | -------------------------------------------------------------------------------- /test/store/root-saga.js: -------------------------------------------------------------------------------- 1 | import {delay, put, takeEvery} from 'redux-saga/effects' 2 | 3 | import { 4 | GET_ASYNC_REDUX_SAGA_PROP_TYPE, 5 | GET_ASYNC_REDUX_SAGA_PROP_TYPE_SUCCESS, 6 | ASYNC_REDUX_SAGA_PROP_TEXT, 7 | } from '../constants' 8 | 9 | const TEST = process.env.NODE_ENV === 'test' 10 | 11 | function* getAsyncReduxSagaProp() { 12 | yield delay(TEST ? 100 : 2000) 13 | 14 | yield put({ 15 | type: GET_ASYNC_REDUX_SAGA_PROP_TYPE_SUCCESS, 16 | data: ASYNC_REDUX_SAGA_PROP_TEXT, 17 | }) 18 | } 19 | 20 | function* rootSaga() { 21 | yield takeEvery(GET_ASYNC_REDUX_SAGA_PROP_TYPE, getAsyncReduxSagaProp) 22 | } 23 | 24 | export default rootSaga 25 | -------------------------------------------------------------------------------- /test/store/store-wrapper.js: -------------------------------------------------------------------------------- 1 | import {applyMiddleware, createStore} from 'redux' 2 | import createSagaMiddleware from 'redux-saga' 3 | import {createWrapper} from 'next-redux-wrapper' 4 | 5 | import rootReducer from './root-reducer' 6 | import rootSaga from './root-saga' 7 | 8 | const makeStore = context => { 9 | const sagaMiddleware = createSagaMiddleware() 10 | const store = createStore( 11 | rootReducer, 12 | applyMiddleware(sagaMiddleware), 13 | ) 14 | 15 | store.sagaTask = sagaMiddleware.run(rootSaga) 16 | 17 | return store 18 | } 19 | 20 | const wrapper = createWrapper(makeStore) 21 | 22 | export default wrapper 23 | -------------------------------------------------------------------------------- /test/utils/create-snapshot.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {shallow} from 'enzyme' 3 | import toJson from 'enzyme-to-json' 4 | 5 | function createSnapshot(WrappedComponent, props) { 6 | // Render is called by Next.js at runtime. 7 | const component = shallow().dive() 8 | 9 | // Remove store from snapshots 10 | const json = toJson(component) 11 | delete json.props.store 12 | 13 | expect(json).toMatchSnapshot() 14 | } 15 | 16 | export default createSnapshot 17 | -------------------------------------------------------------------------------- /test/utils/get-initial-props.js: -------------------------------------------------------------------------------- 1 | function getInitialProps(WrappedComponent) { 2 | // getInitialProps is called by Next.js at runtime. 3 | return WrappedComponent.getInitialProps({ctx: {}}) 4 | } 5 | 6 | export default getInitialProps 7 | -------------------------------------------------------------------------------- /test/utils/get-server-context.js: -------------------------------------------------------------------------------- 1 | function getServerContext() { 2 | return {ctx: {req: {}, res: {}}} 3 | } 4 | 5 | export default getServerContext 6 | --------------------------------------------------------------------------------