GithubHelp home page GithubHelp logo

leoasis / redux-immutable-state-invariant Goto Github PK

View Code? Open in Web Editor NEW
937.0 6.0 37.0 39 KB

Redux middleware that detects mutations between and outside redux dispatches. For development use only.

License: MIT License

JavaScript 99.11% HTML 0.89%

redux-immutable-state-invariant's Introduction

redux-immutable-state-invariant

Build Status Coverage Status

Redux middleware that spits an error on you when you try to mutate your state either inside a dispatch or between dispatches. For development use only!

Why?

Because you're not allowed to mutate your state in your reducers! And by extension, you shouldn't mutate them either outside. In order to change state in your app, you should always return a new instance of your state with the changes.

If you're using a library such as Immutable.js, this is automatically done for you since the structures provided by that library don't allow you to mutate them (as long as you don't have mutable stuff as values in those collections). However, if you're using regular objects and arrays, you should be careful to avoid mutations.

How to install

This lib is intended to use only during development. Don't use this in production!

npm install --save-dev redux-immutable-state-invariant

How to use

As said above, don't use this in production! It involves a lot of object copying and will degrade your app's performance. This is intended to be a tool to aid you in development and help you catch bugs.

To use it, just add it as a middleware in your redux store:

const {applyMiddleware, combineReducers, createStore} = require('redux');
const thunk = require('redux-thunk');
const reducer = require('./reducers/index');

// Be sure to ONLY add this middleware in development!
const middleware = process.env.NODE_ENV !== 'production' ?
  [require('redux-immutable-state-invariant').default(), thunk] :
  [thunk];

// Note passing middleware as the last argument to createStore requires redux@>=3.1.0
const store = createStore(
  reducer,
  applyMiddleware(...middleware)
);

Then if you're doing things correctly, you should see nothing different. But if you don't, that is, if you're mutating your data somewhere in your app either in a dispatch or between dispatches, an error will be thrown with a (hopefully) descriptive message.

API

immutableStateInvariantMiddleware({ isImmutable, ignore })

The default export is a factory to create the middleware. Supports an options argument (optional) to customize middleware behavior. It returns the middleware function when called. The following properties are supported in options:

Options

  • isImmutable function(value) - Specify if a value should be treated as immutable or not. The default implementation will return true for primitive types (like numbers, strings, booleans, null and undefined). Useful if some state of your app uses a library like Immutable.js and you want to let the middleware know that the data structures are indeed immutable.

  • ignore string[] - specify branch(es) of the state object to ignore when detecting for mutations. The elements of the array should be dot-separated "path" strings that match named nodes from the root state.

    // example: ignore mutation detection along the 'foo' & 'bar.thingsToIgnore' branches
    const middleware = immutableStateInvariantMiddleware({
      ignore: [
        'foo',
        'bar.thingsToIgnore'
      ]
    });

redux-immutable-state-invariant's People

Contributors

andersdjohnson avatar darthrellimnad avatar felipemsantana avatar gaearon avatar jackielii avatar jebeck avatar lelandrichardson avatar leoasis avatar matthieu-foucault avatar nkrigsman avatar rivertam avatar timche avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

redux-immutable-state-invariant's Issues

Test Coverage

A test coverage and badge befits this elegant library

Uncaught RangeError: Maximum call stack size exceeded

Hi, I'm getting "Uncaught RangeError: Maximum call stack size exceeded" errors and I'm not seeing circular references in my state. Any ideas on what I should be looking at?

{
  userContext: {},
  items: [
    {
      itemId: 0,
      frontLineItemId: 15,
      name: 'BUD 4/6 CAN 8OZ',
      vendorItemNumber: '00002',
      description: 'BUD 4/6 CAN 8OZ',
      caseUpc: '018200114014',
      um: 'CA',
      supplier: 'InBev',
      frontline: 34.12,
      status: 'Published',
      remarks: 'Remarks for 00002'
    },
    {
      itemId: 1,
      frontLineItemId: 16,
      name: 'LAND SHARK 6/4-16 CANS',
      vendorItemNumber: '00004',
      description: 'LAND SHARK 6/4-16 CANS',
      caseUpc: '00018200962417',
      um: 'CA',
      supplier: 'InBev',
      frontline: 31.12,
      status: 'Published',
      remarks: 'Remarks for 00004'
    },
    {
      itemId: 2,
      frontLineItemId: 17,
      name: 'BUD 12/32 NR',
      vendorItemNumber: '00005',
      description: 'BUD 12/32 NR',
      caseUpc: '00018200110320',
      um: 'CA',
      supplier: 'InBev',
      frontline: 32.12,
      status: 'Draft',
      remarks: 'Remarks for 00005'
    },
    {
      itemId: 3,
      frontLineItemId: 18,
      name: 'BUD 4/6-12 CANS',
      vendorItemNumber: '00007',
      description: 'BUD 4/6-12 CANS',
      caseUpc: '00018200110344',
      um: 'CA',
      supplier: 'InBev',
      frontline: 33.12,
      status: 'Published',
      remarks: 'Remarks for 00007'
    }
  ],
  pricegroups: [
    {
      id: 3,
      name: 'No Tax',
      status: 'ACTIVE',
      description: 'No Tax Zone',
      assignments: 0
    },
    {
      id: 4,
      name: 'Non-Alcs',
      status: 'ACTIVE',
      description: 'Non Alcs Zone',
      assignments: 0
    },
    {
      id: 1,
      name: 'Off Prem',
      status: 'ACTIVE',
      description: 'Off-Premise Zone',
      assignments: 0
    },
    {
      id: 2,
      name: 'On Prem',
      status: 'ACTIVE',
      description: 'On-Premise Zone',
      assignments: 0
    }
  ],
  promotions: [],
  error: {},
  busyIndicator: {
    busyCount: 0
  },
  rightPanel: {
    show: true,
    panelType: 'ITEMS_DETAILS',
    panelProps: {
      rowId: {}
    }
  },
  form: {},
  routing: {
    location: {
      pathname: '/items',
      search: '',
      hash: '',
      key: 'oj6slt'
    }
  }
}

Slow when action contains a Event object

The cause of the problem is at:

The problem:
In react there is this bit of code:
onClick={this.props.goBack}
When the user click on this button, it calls react-router-redux's goBack action

In React router redux middleware:

function goBack(...args) {
  return {
    ...
    payload: { method: 'goBack', args }
  }
}

The following happened:

  1. user click on the button
  2. goBack fired with arguments of ReactProxy, and ReactEvent
  3. react-router-redux returns an action with payload of the above arguments
  4. redux-immutable-state-invariant stringify the whole react proxy and event tree.

It takes over a minute for that stringify to finish

Arguably the component author should've written:
onClick={() => this.props.goBack()}
However this is not a good practice of having another level of function just to work around the problem

If I understand correctly, the stringify here is only for displaying an error message? I reckon it should not try so hard to strigify everything.

Chrome Error: Maximum Call Stack Exceeded

I think it's a very important library for the Redux community.

I installed the library locally according to the instructions. Here is a production version of the app https://next-f2cee.firebaseapp.com/. I expect there to be some mutations and I wonder where. However I received this error, which doesn't seem to relate to a redux/state mutation:
image

Here is my index.js:

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware, compose } from 'redux'
import nextApp from './reducers'
import AppContainer from './containers/AppContainer.js'
import thunk from 'redux-thunk'

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const middlewares = process.env.NODE_ENV !== 'production' ?
  [require('redux-immutable-state-invariant').default(), thunk] :
  [thunk];

  const store = createStore(nextApp, composeEnhancers(applyMiddleware(...middlewares)));

//let store = createStore(nextApp)

render(
  <Provider store={store}>
    <AppContainer />
  </Provider>,
  document.getElementById('root')
)

redux-saga compatibility

So far redux-immutable-state-invariant gives warnings in the react components and in the reducers. but i'm not getting any errors from the sagas.
Is there a plan for making this library work with generator functions needed for redux-saga?

doesn't work with redux-devtools-extension

I use the following TypeScript code to setup Redux' middlewares:
(Note: for dev purposes only)

if (typeof window !== 'undefined') {
  if ((window as any).__REDUX_DEVTOOLS_EXTENSION__) {
    enhancer = compose(
      installReduxLoop(),
      (window as any).__REDUX_DEVTOOLS_EXTENSION__({ trace: true, traceLimit: 25 }),
      applyMiddleware(require('redux-immutable-state-invariant').default())
    )
  }
  // ...
}

I have found that I need to comment out the call to __REDUX_DEVTOOLS_EXTENSION__(...) in order for redux-immutable-state-invariant to work properly, otherwise incorrect mutations are detected for everything. Perhaps a note could be added that the 2 middlewares/extensions are incompatible? Thanks.

Usage with "in-flight", non-serialisable actions.

As discussed here, FSA compliant actions can be non-serialisable while they are waiting for a promise to resolve, causing errors when trying to use this lib (JSON.serialize() fails).

I have a small example todo app that uses redux-actions and redux-promises, and I would love to find a way to add redux-immutable-state-invariant to it.

Not sure how to go about doing that though, and I'm not sure my understanding of the problem is correct.

I would guess though that the "isFSA()" and "isSerializable()" methods discussed in the issue mentioned above could be used to filter out actions that should not be checked. But then again, I'm not sure this lib should necessarily know too much about FSA... Perhaps it could be as easy as checking if the payload is a Promise?

Or is it better to find a way to completely avoid non-serialisable actions?

Maybe @acdlite has some input?

getState() is undefined

Got this error when adding this to my dev middleware.

const middleware = [
  require('redux-immutable-state-invariant').default,
  thunk,
  promise,
]
const store = createStore(
  rootReducer,
  initialState,
  compose(
    applyMiddleware(...middleware),
    DevTools.instrument() // Enable Redux DevTools
  )
)

"redux": "version": "3.6.0"

screen shot 2017-05-12 at 09 27 32

Error after updating state

Hi,

I'm building tree view using d3 visualizations where user can add or remove the nodes from the tree. To build the tree, data is passed in JSON format. I'm updating the JSON object and saving the new values in the store. But after saving I'm getting "Maximum calls stack size exceeded" error.

Please suggest me how to fix this issue.

Thanks.

Is it really an invariant to use Animated.event on redux state?

Hey :)

Working with the Animated lib on React Native, and I'm getting warnings from this (excellent) module that I'm being naughty.

Essentially, I have a list that updates an Animated.Value onScroll as pr the docs:

<AnimatedFlatlist
    scrollEventThrottle={16}
    onScroll={Animated.event(
            [
              {
                nativeEvent: {
                  contentOffset: { y: this.props.commonState.feedScrollAnim },
                },
              },
            ],
            { useNativeDriver: true }
      )}     
      ...

According to the docs this will map the output of the event to the given value in native land, and not in the JS VM, as far as I understand. As such, I'm not sure it should be regarded as an invariant violation, but I'm not sure?

Disabling this module, everything works just fine. But I'd prefer to have it on, or to have some way of suppressing the redbox for this use case in particular - any way to do that?

Thanks ๐Ÿ‘

Slow on a big state tree partially managed by immutable.js

I happen to have 8 reducers, where 6 of them are immutable objects. I use redux-immutable-state-invariant to make sure the remaining 2 reducers respect immutability too.

As you can see below, the Chrome profile reports that 50% of the CPU time is used by redux-immutable-state-invariant. This made my interface unresponsive.

screen shot 2016-08-30 at 19 18 52

May you should add a warning in the 'how to use' section of the repository?

Error setting up

Hi @leoasis! I was looking to try your library but ran into the following error right away. (Apologies if this is a n00b error)

RangeError: Maximum call stack size exceeded
[1]     at trackProperties (node_modules/redux-immutable-state-invariant/dist/trackForMutations.js:18:3)
[1]     at trackProperties (node_modules/redux-immutable-state-invariant/dist/trackForMutations.js:24:31)

Relevant code setting up:

let middleware = [createMiddleware(client), transitionMiddleware];
  if (__DEVELOPMENT__) {
    const logger = createLogger({
    logger: console
    });

   const immutableState = require('redux-immutable-state-invariant')();
    middleware = [
    createMiddleware(client),
    transitionMiddleware,
    logger,
    immutableState
    ];
  }

  finalCreateStore = applyMiddleware(...middleware)(_createStore);

I am running "redux": "^3.0.5" if that helps diagnose.

Any help would be much appreciated, and thank you for the library!

Consider deprecating in favor of Redux Toolkit

First off, I want to offer sincere thanks to the maintainers of this library. It's been a huge help to many people, and we used it in Redux Toolkit up until v1.3.0. In that release, we ported several of our dependencies directly into the RTK codebase to cut down on number of external deps and improve maintainability ( https://github.com/reduxjs/redux-toolkit/releases/tag/v1.3.0 ). One of those was redux-immutable-state-invariant.

Since then, we've made several improvements to our version of the middleware, including warning if the middleware takes too long to run (32ms by default).

In our new https://github.com/reduxjs/redux-toolkit/releases/tag/v1.5.1 release, we've made huge improvements to the runtime thanks to assumptions like working with frozen state from Immer, as well as optimization of the keypath handling by switching from arrays to strings.

RTK includes our immutability middleware in configureStore by default, so most people should be getting it automatically these days. But, since we export the immutability middleware too, people could just add RTK as a dependency and import the middleware if they wanted to for some reason.

So, at this point our version of the middleware is significantly faster and has several other improvements. My suggestion would be to deprecate this package and point people to use RTK instead.

Thanks again for all your work here!

Does redux-immutable-state-invariant detect changes over Map<string, MyObject>?

I am mutating my mapProperty from my state, and redux-immutable-state-invariant, does not inform mutation to me. Mutating state line:

state.mapRecursosIndxByCtx.set("asdf", new ZRecursoViewModel());

mapRecursosIndxByCtx property is defined as follows:

const initialState: ZAplication.ZAplicationState = { mapRecursosIndxByCtx: new Map<string, ZRecursoViewModel>() }

It works when i try to mutate another properties but mapRecursosIndxByCtx property doesn't works.

Am i right?.

Thanks in advance.

Structural Sharing

In my reducer Iยดm copying this object...

const myObject = {
    text: "test",
    children: ["one", "two"]
}

...doing this:

const myCopy = {
    ...myObject
}

The middleware throws an error. Shouldn't this work? I know I'm reusing the array but this would be doning "structural sharing". I'm not mutating the original object and I'm wasting less memory.

Is this correct or am I understanding something wrong?

In this case I have to copy like this to fix the error:

const myCopy = {
    ...myObject,
    children: [...myObject.children]
}

Thanks!

Error: Object doesn't support property or method 'isNaN' on IE11

This issue only happened on IE 11 browser. It should because method Number.isNaN used in below code is not supported in IE 11.

trackForMutations.js (49, 3)

  if (sameParentRef && !sameRef && !Number.isNaN(obj)) {
    return { wasMutated: true, path: path };
  }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.