Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

next-redux-wrapper

Package Overview
Dependencies
Maintainers
1
Versions
48
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

next-redux-wrapper - npm Package Compare versions

Comparing version 1.3.5 to 2.0.0-beta.2

lib/index.js

67

package.json
{
"name": "next-redux-wrapper",
"version": "1.3.5",
"version": "2.0.0-beta.2",
"description": "Redux wrapper for Next.js",
"main": "src/index.js",
"main": "lib/index.js",
"scripts": {
"test": "jest",
"start": "next",
"build": "next build",
"serve": "next start"
"test": "NODE_ENV=test jest",
"clean": "rimraf lib",
"babel": "babel --ignore spec.js,test.js --out-dir lib src",
"build": "npm run clean && npm run babel",
"next": "next",
"watch": "npm run babel -- --watch",
"start": "npm-run-all -p watch next",
"prepublishOnly": "npm run build && npm test"
},
"author": "Kirill Konshin",
"dependencies": {
"@babel/runtime": "^7.0.0-beta.42"
},
"devDependencies": {
"babel-jest": "^20.0.3",
"babel-preset-react-app": "^3.0.1",
"jest": "^20.0.4",
"next": "^2.4.3",
"react": "^15.4.2",
"react-dom": "^15.6.1",
"react-redux": "^5.0.2",
"react-test-renderer": "^15.4.2",
"redux": "^3.6.0",
"redux-promise-middleware": "^4.2.0"
"@babel/cli": "7.0.0-beta.42",
"@babel/core": "7.0.0-beta.42",
"@babel/plugin-transform-runtime": "7.0.0-beta.42",
"@types/jest": "22.2.3",
"@types/jest-environment-puppeteer": "2.2.0",
"@types/puppeteer": "1.2.1",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "22.4.3",
"jest": "22.4.3",
"jest-puppeteer": "2.4.0",
"next": "canary",
"npm-run-all": "4.1.2",
"puppeteer": "1.3.0",
"react": "16.3.2",
"react-dom": "16.3.2",
"react-redux": "5.0.7",
"react-test-renderer": "16.2.0",
"redux": "4.0.0",
"redux-promise-middleware": "5.1.1",
"rimraf": "2.6.2"
},
"peerDependencies": {
"react": "*",
"react-redux": "*"
"react-redux": "*",
"next": ">=6.0.0 || 6.0.0-* || canary"
},

@@ -40,16 +58,3 @@ "repository": {

},
"license": "MIT",
"jest": {
"testRegex": "(/src/.*\\.spec.js)$",
"collectCoverage": true,
"testEnvironment": "jsdom"
},
"babel": {
"presets": [
"react-app"
]
},
"dependencies": {
"object.assign": "^4.0.4"
}
"license": "MIT"
}
Redux wrapper for Next.js
=========================
![Build status](https://travis-ci.org/kirill-konshin/next-redux-wrapper.svg?branch=master)
## Usage
:warning: This will work only with NextJS 6.x :warning:
If you're looking for a version for NextJS 5.x (the one for individual pages) use [1.x branch](https://github.com/kirill-konshin/next-redux-wrapper/tree/1.x).
- [Usage](#usage)
- [How it works](#how-it-works)
- [Async actions in `getInitialProps`](#async-actions-in-getinitialprops)
- [Usage with Immutable.JS](#usage-with-immutablejs)
- [Usage with Redux Persist](#usage-with-redux-persist)
- [Resources](#resources)
## Installation
```bash
npm install next-redux-wrapper --save
npm install next-redux-wrapper@next --save
```
Wrapper has to be attached your page components (located in `/pages`). For safety it is recommended
to wrap all pages, no matter if they use Redux or not, so that you should not care about it anymore in
all child components.
Wrapper has to be attached your `_app` component (located in `/pages`). All other pages may use regular `connect`
function of `react-redux`.

@@ -18,5 +29,6 @@ Here is the minimal setup (`makeStore` and `reducer` usually are located in other files):

```js
import React, {Component} from "react";
// pages/_app.js
import React from "react";
import {createStore} from "redux";
import withRedux from "next-redux-wrapper";
import {withReduxApp} from "next-redux-wrapper";

@@ -43,2 +55,33 @@ const reducer = (state = {foo: ''}, action) => {

class MyApp extends React.Component {
static async getInitialProps({Component, ctx}) {
// we can dispatch from here too
ctx.store.dispatch({type: 'FOO', payload: 'foo'});
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
return {pageProps};
}
render() {
const {Component, pageProps} = this.props;
return (
<Component {...pageProps} />
);
}
}
export default withReduxApp(makeStore)(MyApp);
```
And then actual page components can be simply connected:
```js
import React, {Component} from "react";
import {connect} from "react-redux";
class Page extends Component {

@@ -70,28 +113,15 @@ static getInitialProps({store, isServer, pathname, query}) {

The `withRedux` function accepts `makeStore` as first argument, all other arguments are internally passed to React
Redux's `connect()` function for simplicity. The `makeStore` function will receive initial state as one argument and
should return a new instance of redux store each time when called, no memoization needed here, it is automatically done
The `withRedux` function accepts `makeStore` as first argument. The `makeStore` function will receive initial state as one argument and
should return a new instance of Redux `store` each time when called, no memoization needed here, it is automatically done
inside the wrapper.
`withRedux` also optionally accepts an object. In this case only 1 parameter is passed which can contain the following
configuration properties:
When `makeStore` is invoked it is also provided with a configuration object as the second parameter, which includes:
- `createStore` (required, function) : the `makeStore` function as described above
- `storeKey` (optional, string) : the key used on `window` to persist the store on the client
- `debug` (optional, boolean) : enable debug logging
- `mapStateToProps`, `mapDispatchToProps`, `mergeProps` (optional, functions) : functions to pass to `react-redux` `connect` method
- `connectOptions` (optional, object) : configuration to pass to `react-redux` `connect` method
When `makeStore` is invoked it is also provided a configuration object as the second parameter, which includes:
- `isServer` (boolean): `true` if called while on the server rather than the client
- `req` (Request): The `next.js` `getInitialProps` context `req` parameter
- `res` (Request): The `next.js` `getInitialProps` context `req` parameter
- `query` (object): The `next.js` `getInitialProps` context `query` parameter
The object also includes all configuration as passed to `withRedux` if called with an object of configuration properties.
The object also includes all configuration as passed to `withRedux`.
**Use `withRedux` to wrap only top level pages! All other components should keep using regular `connect` function of
React Redux.**
Although it is possible to create server or client specific logic in both `createStore` function and `getInitialProps`

@@ -101,2 +131,5 @@ method I highly don't recommend to have different behavior. This may cause errors and checksum mismatches which in turn

Keep in mind that whatever you do in `_app` is also affecting the NextJS error page, so if you `dispatch`,
set something on `req` and checki it to prevent double `dispatch`.
I don't recommend to use `withRedux` in both top level pages and `_document.js` files, Next.JS

@@ -150,2 +183,106 @@ [does not have provide](https://github.com/zeit/next.js/issues/1267) a reliable way to determine the sequence when

## Usage with Redux Persist
Honestly, I think that putting a persistence gate is not necessary because server can already send *some* HTML with *some* state, so it's better to show it right away and then wait for `REHYDRATE` action to happen to show additional delta coming from persistence storage. That's why we use Server Side Rendering in a first place.
But, for those who actually want to block the UI while rehydration is happening, here is the solution (still hacky though).
```js
// lib/redux.js
import logger from 'redux-logger';
import {applyMiddleware, createStore} from 'redux';
const SET_CLIENT_STATE = 'SET_CLIENT_STATE';
export const reducer = (state, {type, payload}) => {
if (type === SET_CLIENT_STATE) {
return {
...state,
fromClient: payload
};
}
return state;
};
const makeConfiguredStore = (reducer, initialState) =>
createStore(reducer, initialState, applyMiddleware(logger));
export const makeStore = (initialState, {isServer, req, debug, storeKey}) => {
if (isServer) {
initialState = initialState || {fromServer: 'foo'};
return makeConfiguredStore(reducer, initialState);
} else {
// we need it only on client side
const {persistStore, persistReducer} = require('redux-persist');
const storage = require('redux-persist/lib/storage').default;
const persistConfig = {
key: 'nextjs',
whitelist: ['fromClient'], // make sure it does not clash with server keys
storage
};
const persistedReducer = persistReducer(persistConfig, reducer);
const store = makeConfiguredStore(persistedReducer, initialState);
store.__persistor = persistStore(store); // Nasty hack
return store;
}
};
export const setClientState = (clientState) => ({
type: SET_CLIENT_STATE,
payload: clientState
});
```
And then in NextJS `_app` page:
```js
// pages/_app.js
import React from "react";
import withRedux from "next-redux-wrapper";
import {makeStore} from "./lib/redux";
import {PersistGate} from 'redux-persist/integration/react';
export default withRedux(makeStore, {debug: true})(class MyApp extends React.Component {
render() {
const {Component, pageProps, store} = this.props;
return (
<PersistGate persistor={store.__persistor} loading: {<div>Loading</div>}>
<Component {...pageProps} />
</PersistGate>
);
}
});
```
And then in NextJS page:
```js
// pages/index.js
import React from "react";
import {connect} from "react-redux";
export default connect(
(state) => state,
{setClientState}
)(({fromServer, fromClient, setClientState}) => (
<div>
<div>fromServer: {fromServer}</div>
<div>fromClient: {fromClient}</div>
<div><button onClick={e => setClientState('bar')}>Set Client State</button></div>
</div>
));
```
## Resources

@@ -152,0 +289,0 @@

@@ -1,36 +0,36 @@

var React = require('react');
var ReactRedux = require('react-redux');
var assign = require('object.assign');
import React from "react";
import {Provider} from "react-redux";
import BaseApp, {Container} from "next/app";
var connect = ReactRedux.connect;
var Provider = ReactRedux.Provider;
let _Promise = Promise;
let _debug = false;
const DEFAULT_KEY = '__NEXT_REDUX_STORE__';
var _Promise;
var _debug = false;
var skipMerge = ['initialState', 'initialProps', 'isServer', 'store'];
var DEFAULT_KEY = '__NEXT_REDUX_STORE__';
var isBrowser = typeof window !== 'undefined';
export const setPromise = Promise => _Promise = Promise;
function initStore(makeStore, initialState, context, config) {
var req = context.req;
var isServer = !!req && !isBrowser;
var storeKey = config.storeKey;
/**
* @param makeStore
* @param initialState
* @param ctx
* @param config
* @return {{getState: function, dispatch: function}}
*/
const initStore = (makeStore, initialState, ctx, config) => {
var options = assign({}, config, {
isServer: isServer,
req: req,
res: context.res,
query: context.query
});
const {req} = ctx;
const isServer = !!req;
const {storeKey} = config;
const options = {
...ctx,
...config,
isServer
};
// Always make a new store if server
if (isServer) {
if (!req._store) {
req._store = makeStore(initialState, options);
}
if (!req._store) req._store = makeStore(initialState, options); // page and error should share one store
return req._store;
}
if (!isBrowser) return null;
// Memoize store if client

@@ -43,74 +43,69 @@ if (!window[storeKey]) {

}
};
module.exports = function(createStore) {
/**
* @param createStore
* @param config
* @return {function(App): {getInitialProps, new(): WrappedApp, prototype: WrappedApp}}
*/
export default (createStore, config = {}) => {
var config = {storeKey: DEFAULT_KEY, debug: _debug};
var connectArgs;
config = {storeKey: DEFAULT_KEY, debug: _debug, ...config};
// Ensure backwards compatibility, the config object should come last after connect arguments.
if (typeof createStore === 'object') {
return (App) => (class WrappedApp extends BaseApp {
var wrappedConfig = createStore;
static displayName = `withRedux(${App.displayName || App.name || 'App'})`;
if (!({}).hasOwnProperty.call(wrappedConfig, 'createStore')) {
throw new Error('Missing createStore function');
}
createStore = wrappedConfig.createStore;
static getInitialProps = async (appCtx = {}) => {
// Set all config keys if they exist.
if (({}).hasOwnProperty.call(wrappedConfig, 'debug')) {
config.debug = wrappedConfig.debug;
}
const {ctx: {req} = {}, ctx = {}} = appCtx;
if (({}).hasOwnProperty.call(wrappedConfig, 'storeKey')) {
config.storeKey = wrappedConfig.storeKey;
}
const isServer = !!req;
// Map the connect arguments from the passed in config object.
connectArgs = [
wrappedConfig.mapStateToProps || undefined,
wrappedConfig.mapDispatchToProps || undefined,
wrappedConfig.mergeProps || undefined,
wrappedConfig.connectOptions || undefined,
];
const store = initStore(
createStore,
undefined /** initialState **/,
ctx,
config
);
} else {
connectArgs = [].slice.call(arguments).slice(1);
}
if (config.debug) console.log('1. WrappedApp.getInitialProps wrapper got the store with state', store.getState());
return function(Cmp) {
const initialProps = (App.getInitialProps ? await App.getInitialProps.call(App, {
...appCtx,
ctx: {
...ctx,
store,
isServer
}
}) : {});
// Since provide should always be after connect we connect here
var ConnectedCmp = (connect.apply(null, connectArgs))(Cmp);
if (config.debug) console.log('3. WrappedApp.getInitialProps has store state', store.getState());
function WrappedCmp(props) {
return {
isServer,
store,
initialState: store.getState(),
initialProps: initialProps
};
};
props = props || {};
render() {
var initialState = props.initialState || {};
var initialProps = props.initialProps || {};
var hasStore = props.store && props.store.dispatch && props.store.getState;
var store = hasStore
? props.store
: initStore(createStore, initialState, {}, config); // client case, no store but has initialState
let {initialState, initialProps, store, isServer, ...props} = this.props;
if (!store) {
console.error('Attention, withRedux has to be used only for top level pages, all other components must be wrapped with React Redux connect!');
console.error('Check ' + Cmp.name + ' component.');
console.error('Automatic fallback to connect has been performed, please check your code.');
return React.createElement(ConnectedCmp, props);
}
const hasStore = store && ('dispatch' in store) && ('getState' in store);
if (config.debug) console.log(Cmp.name, '- 4. WrappedCmp.render', (hasStore ? 'picked up existing one,' : 'created new store with'), 'initialState', initialState);
store = hasStore ? store : initStore(createStore, initialState, {}, config); // client case, no store but has initialState
// Fix for _document
var mergedProps = {};
Object.keys(props).forEach(function(p) { if (!~skipMerge.indexOf(p)) mergedProps[p] = props[p]; });
Object.keys(initialProps || {}).forEach(function(p) { mergedProps[p] = initialProps[p]; });
if (config.debug) console.log('4. WrappedApp.render', (hasStore ? 'picked up existing one,' : 'created new store with'), 'initialState', initialState);
return React.createElement( //FIXME This will create double Provider for _document case
Provider,
{store: store},
React.createElement(ConnectedCmp, mergedProps)
// <Container> must be on top of hierarchy for HMR to work
// Cmp render must return something like <Component/>
return (
<Container>
<Provider store={store}>
<App {...initialProps} {...props} store={store} isServer={isServer}/>
</Provider>
</Container>
);

@@ -120,47 +115,4 @@

WrappedCmp.getInitialProps = function(ctx) {
});
return new _Promise(function(res) {
ctx = ctx || {};
if (config.debug) console.log(Cmp.name, '- 1. WrappedCmp.getInitialProps wrapper', (ctx.req && ctx.req._store ? 'takes the req store' : 'creates the store'));
ctx.isServer = !!ctx.req;
ctx.store = initStore(createStore, undefined /** initialState **/, {req: ctx.req, query: ctx.query, res: ctx.res}, config);
res(_Promise.all([
ctx.isServer,
ctx.store,
ctx.req,
Cmp.getInitialProps ? Cmp.getInitialProps.call(Cmp, ctx) : {}
]));
}).then(function(arr) {
if (config.debug) console.log(Cmp.name, '- 3. WrappedCmp.getInitialProps has store state', arr[1].getState());
return {
isServer: arr[0],
store: arr[1],
initialState: arr[1].getState(),
initialProps: arr[3]
};
});
};
return WrappedCmp;
};
};
module.exports.setPromise = function(Promise) {
_Promise = Promise;
};
module.exports.setDebug = function(debug) {
_debug = debug;
};
module.exports.setPromise(Promise);
};

@@ -5,4 +5,6 @@ import React from "react";

import renderer from "react-test-renderer";
import wrapper from "./index";
import withRedux from "./index";
global.window = {}; //FIXME Move to test env https://github.com/smooth-code/jest-puppeteer#extend-puppeteerenvironment
const reducer = (state = {reduxStatus: 'init'}, action) => {

@@ -18,10 +20,8 @@ switch (action.type) {

const makeStore = (initialState) => {
return createStore(reducer, initialState, applyMiddleware(promiseMiddleware()));
};
const makeStore = (initialState) => createStore(reducer, initialState, applyMiddleware(promiseMiddleware()));
class SyncPage extends React.Component {
static getInitialProps({store}) {
store.dispatch({type: 'FOO', payload: 'foo'});
static getInitialProps({ctx}) {
ctx.store.dispatch({type: 'FOO', payload: 'foo'});
return {custom: 'custom'};

@@ -31,6 +31,7 @@ }

render() {
const {store, ...props} = this.props;
return (
<div>
<div className="redux">{this.props.reduxStatus}</div>
<div className="custom">{this.props.custom}</div>
{JSON.stringify(props)}
{JSON.stringify(store.getState())}
</div>

@@ -42,16 +43,11 @@ )

function someAsyncAction() {
return {
type: 'FOO',
payload: new Promise((res) => { res('foo'); })
}
}
const someAsyncAction = ({
type: 'FOO',
payload: new Promise(res => res('foo'))
});
class AsyncPage extends SyncPage {
static getInitialProps({store}) {
const action = someAsyncAction();
store.dispatch(action);
return action.payload.then((payload) => {
return {custom: 'custom'};
});
static async getInitialProps({ctx}) {
await ctx.store.dispatch(someAsyncAction);
return {custom: 'custom'};
}

@@ -76,98 +72,10 @@ }

test('simple store integration', async() => {
const WrappedPage = wrapper(makeStore, state => state)(SyncPage);
test('simple store integration', async () => {
const WrappedPage = withRedux(makeStore)(SyncPage);
await verifyComponent(WrappedPage);
});
test('async store integration', async() => {
const WrappedPage = wrapper(makeStore, state => state)(AsyncPage);
test('async store integration', async () => {
const WrappedPage = withRedux(makeStore)(AsyncPage);
await verifyComponent(WrappedPage);
});
const spyLog = jest.spyOn(global.console, 'log');
describe('createStore', () => {
beforeEach(() => {
spyLog.mockReset();
});
afterEach(() => {
delete window.__NEXT_REDUX_STORE__;
});
test('simple props', () => {
const App = ({foo}) => (<div>{foo}</div>);
const WrappedApp = wrapper(makeStore, state => state)(App);
const component = renderer.create(<WrappedApp foo="foo"/>);
expect(component.toJSON()).toMatchSnapshot();
});
test('advanced props', () => {
const App = ({foo}) => (<div>{foo}</div>);
const WrappedApp = wrapper({ createStore: makeStore, mapStateToProps: (state) => state })(App);
const component = renderer.create(<WrappedApp foo="foo"/>);
expect(component.toJSON()).toMatchSnapshot();
});
test('debug mode from options', async () => {
const App = ({foo}) => (<div>{foo}</div>);
const WrappedApp = wrapper({
createStore: (initialState, options) => {
expect(options.debug).toBe(true);
return createStore(reducer, initialState, applyMiddleware(promiseMiddleware()));
},
debug: true
})(App);
const component = renderer.create(<WrappedApp/>);
await WrappedApp.getInitialProps();
expect(spyLog).toHaveBeenCalledTimes(3);
expect(component.toJSON()).toMatchSnapshot();
});
test('debug mode with setDebug method', async () => {
const App = ({foo}) => (<div>{foo}</div>);
wrapper.setDebug(true);
const WrappedApp = wrapper((initialState, options) => {
expect(options.debug).toBe(true);
return createStore(reducer, initialState, applyMiddleware(promiseMiddleware()));
})(App);
const component = renderer.create(<WrappedApp/>);
await WrappedApp.getInitialProps();
expect(spyLog).toHaveBeenCalledTimes(3);
expect(component.toJSON()).toMatchSnapshot();
});
test('should throw if no createStore method', async () => {
const App = ({foo}) => (<div>{foo}</div>);
expect(() => wrapper({
debug: true
})(App)).toThrow();
});
test('should be able to configure store key on window', async () => {
const App = ({foo}) => (<div>{foo}</div>);
const WrappedApp = wrapper({
createStore: makeStore,
storeKey: 'TESTKEY'
})(App);
const component = renderer.create(<WrappedApp/>);
await WrappedApp.getInitialProps();
expect(window.__NEXT_REDUX_STORE__).not.toBeDefined();
expect(window.TESTKEY).toBeDefined();
expect(component.toJSON()).toMatchSnapshot();
delete window.TESTKEY;
});
test('should memoize store on client in window', async() => {
const App = ({foo}) => (<div>{foo}</div>);
const App1 = wrapper(makeStore, state => state)(App);
renderer.create(<App1/>);
expect(window.__NEXT_REDUX_STORE__).toBeDefined();
const App2 = wrapper((initialState, options) => {
throw new Error('New store should not be created!');
}, state => state)(App);
renderer.create(<App2 foo="foo"/>);
expect(window.__NEXT_REDUX_STORE__).toBeDefined();
});
});
});

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc