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

Multiple MobX instances in your application #1082

Closed
mweststrate opened this issue Jul 6, 2017 · 75 comments
Closed

Multiple MobX instances in your application #1082

mweststrate opened this issue Jul 6, 2017 · 75 comments

Comments

@mweststrate
Copy link
Member

mweststrate commented Jul 6, 2017

MobX has some internal global state to be able to track consumers of observables, schedule reactions etc.
For this reason, the rule of thumb is that there should be only one instance of the MobX library in your application. Otherwise, if you have two libraries with their own dependency; reactions created in one library, will not react to observables created with the other library.

Since version 4.2 you will be warned about this:

There are multiple mobx instances active. See https://github.com/mobxjs/mobx/issues/1082 for details.

Solutions to this warning are:

1. If there should be only one mobx instance (libraries should cooperate & react to each other)

Use peer dependencies

If you intend the difference libraries you use to react properly to each other. In that case, those libraries should not declare mobx as a dependency, but as peer dependency instead. If you are bundling your libraries independently, make sure to mark mobx as an external dependency. The only place where mobx should be used as a direct dependency is in the "end product", which will provide a single mobx version to all libraries needing it. If you did that properly, the above warning will disappear

Note that it is possible to use mobx.extras.shareGlobalState(), this will signal multiple mobx instances to share their global state (if the internal version differences aren't too big). This option mainly exists for legacy reasons; peer dependencies is the neater approach to this problem. shareGlobalState will be removed in 4.0

2. If there are intentionally multiple mobx instances (libraries run in isolation)

Use mobx.extra.runInIsolation

In rare cases, you might be building a dynamic system that allows for third party plugins, or you are building such a plugin. In those cases you might be using mobx just for internal purposes inside the plugin, or in the hosting system. However, if you don't intend to use mobx as a communication layer between the plugin and plugin host (which is a very powerful mechanism btw!), you can suppress the above warning by calling mobx.extras.isolateGlobalState(). This signals mobx that it is the intention that multiple mobx instances are active in your application, without tracking each other, and will suppress the above warning.

See #621, #1018, #1066, #1071

3. More extensive trouble shouting guide

See below

@danielkcz
Copy link
Contributor

When using mobx.extras.isolateGlobalState() is the call to useStrict() isolated as well? Is seems to me it's not so basically when I want to enable it in my app, but the module running MobX does not care about strict, it will start throwing exceptions.

@mweststrate
Copy link
Member Author

mweststrate commented Aug 1, 2017 via email

@aksonov
Copy link

aksonov commented Aug 2, 2017

@mweststrate RNRF v4 uses mobx internally, but I don't want non-mobx users to require to install mobx (if I make it as peerDependency). Is there any better solution?

@danielkcz
Copy link
Contributor

@aksonov Calling mobx.extras.isolateGlobalState() works well, I think it should be called by RNRF by default with option to disable such behavior in case someone would want it shared.

@mweststrate
Copy link
Member Author

mweststrate commented Aug 2, 2017 via email

@danielkcz
Copy link
Contributor

@mweststrate I tried to prepare some example in codesandbox by using regular module import alongside the external umd bundle. Unfortunately, I cannot seem to use mobx-react as external dependency. It's failing with Uncaught TypeError: Cannot use 'in' operator to search for 'default' in undefined when loading from CNDJS.

https://codesandbox.io/s/pQQ2rOxZ2

I am not sure where how I could setup environment to replicate useStatic possible issue. Going with hassle of publishing some testing module seems like overkill.

@ianshea
Copy link

ianshea commented Aug 3, 2017

We ran into this and discovered we had accidentally imported mobx from two different files in the package.

import {computed} from "mobx";

and in another file from

import {computed} from "mobx/lib/mobx";

Adjusting the second import to just mobx cleared up the issue and resulted in a nice file size savings too. 👍

@mikecann
Copy link

I was forwarded here by the console warning:

[mobx] Warning: there are multiple mobx instances active. This might lead to unexpected results. See #1082 for details.

I have a "library" package that has a number of react components that utilize mobx and im trying to share between my apps which also are using mobx.

I have setup my library project.json with peer dependencies like so:

{
    "name": "my-shared-lib",
    "version": "1.0.0",
    "private": true,
    "main": "dist/index.js",
    "types": "dist/index.d.ts",
    "target": "es5",
    "lib": [
        "es6",
        "dom"
    ],
    "jsx": "react",
    "scripts": {
        "link": "npm link",
        "build": "tsc",
        "start": "tsc -w"
    },
    "devDependencies": {
        ...
        "mobx": "^3.2.2",
        "mobx-react": "^4.2.2",
        "mobx-react-router": "^4.0.1",
        "react": "^15.6.1",
        "react-addons-css-transition-group": "^15.6.0",
        "react-bootstrap": "^0.31.2",
        "react-dom": "^15.6.1",
        "react-markdown": "^2.5.0",
        "react-router": "^4.2.0",
        "react-router-dom": "^4.2.2",
        "react-tag-input": "^4.7.2"
    },
    "peerDependencies": {
        "mobx": "^3.2.2",
        "mobx-react": "^4.2.2",
        "mobx-react-router": "^4.0.1",
        "react": "^15.6.1",
        "react-addons-css-transition-group": "^15.6.0",
        "react-bootstrap": "^0.31.2",
        "react-dom": "^15.6.1",
        "react-markdown": "^2.5.0",
        "react-router": "^4.2.0",
        "react-router-dom": "^4.2.2",
        "react-tag-input": "^4.7.2"
    }    
}

and the other projects are consuming using local dependencies: "my-shared-lib": "file:../shared",

which I think is correct but im still getting that warning...

I am using npm link to make things easier for development too so im not sure if thats causing issues... any ideas?

Does anyone have an example of this working?

@johnnyreilly
Copy link

Hi @mweststrate,

I'm just spinning up a new project and we're planning to use mobx for our state management. Your project makes it much simpler to do this than Redux (in our team's view) so thankyou! ❤️

We're building up a seed project right now and are experiencing this message:

[mobx] Warning: there are multiple mobx instances active. This might lead to unexpected results. See https://github.com/mobxjs/mobx/issues/1082 for details.

I'm at a loss as to what might be causing this and so I wondered if I could trouble you for pointers?

We're using mobx and mobx-react so we've set up both as peer dependencies; see the relevant part of our package.json:

  "dependencies": {
    "axios": "^0.16.1",
    "core-js": "^2.4.1",
    "history": "^4.6.1",
    "mobx": "^3.2.2",
    "mobx-react": "^4.2.2",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-router": "^4.1.1",
    "react-router-dom": "^4.1.1"
  },
  "peerDependencies": {
    "mobx": "^3.2.2",
    "mobx-react": "^4.2.2"
  }

Our imports tend to look like this:

import { inject, observer } from 'mobx-react';
// or
import { observable, action } from 'mobx';

Can you think of anything that might be causing this issue? Or give me any ideas of how I could isolate this? Also, I've seen mention of mobx-extra but I can't find any docs on it. Forgive me if this is a somewhat silly question; I'm a mobx noob.

@mweststrate
Copy link
Member Author

Note that you don't need to set up peer dependencies if you are depending on mobx already. It is an either setup: Either you are creating a library, intended to be used in a host package, and you use peer deps, or you are the host project, and you have mobx / mobx-react as direct dependency and don't need peers. Rule of thumb: if react is a dep, then mobx should be a dep. If react is a peerDep / external, then mobx should be :)

@johnnyreilly
Copy link

johnnyreilly commented Sep 19, 2017

Sure - that's helpful. Nothing depends on us; we're the host project. So I've dropped the peerDependencies. However the issue still exists.

In case it's relevant, we're using webpack for bundling and experiencing this during watch mode. Could that be triggering this in any way?

Other things that might be relevant: our codebase is TypeScript.

@mikecann
Copy link

mikecann commented Sep 19, 2017 via email

@johnnyreilly
Copy link

That would be awesome @mikecann - a repro repo speaks a thousand words and all that.

@mikecann
Copy link

@johnnyreilly @mweststrate Okay I created a sample repo that demonstrates the issue:
https://github.com/mikecann/mobx-multiple-instance-issue-sample

As mentioned in the readme, im not sure if it has something to do with the post-install build script or something as it only happens if you use yarn not npm.

@johnnyreilly
Copy link

Well done! I'm also using yarn in case that's relevant

@mweststrate
Copy link
Member Author

mweststrate commented Sep 21, 2017

@mikecann not exactly sure what the problem is in your setup, but there sees to be something iffy:

  1. mobx is a dep of shared
  2. shared is a relative dep of client
  3. installing shared in client will cause its deps also to be installed
  4. this will probably result in mobx being present in both the node_modules of client and shared
  5. module resolution is always relative to the requiring file. Depending on how the 'install' happens (linking or installing) that will resolve mobx from either the client or shared folder (this might be a difference between yarn and nmp)

The current behavior is probably explainable in both cases, but in my general experience linking or relatively installing modules always messes up the dep tree (unrelated to mobx itself, this often caused troubles with webpack loaders, or utility libraries like lodash and such for as well).

Since client is your host package, what the setup should be looking like is at least mobx as dep of client, and peer dep of shared. If you want to be able to compile shared stand alone, that might require mobx also to be a dev dep (for the typings).

The risk of the latter is that if linking the packages; the dev dep is still picked up from 'shared' node modules (because it happens to be there)

One way to solve that issue is to use module aliases in webpack or paths in tsconfig to make sure the module resolves to the correct node_modules.

However, there is one much simpler solution: Put all the 'host' dependencies in the root package.json; where both client and shared can resolve it (node recursively resolves up in the dir structure, regardless where package.json resides). For us that solved the problem in a similar setup where we had closely coupled packages. All other problems where fixable as well by scriptings and such, but still would blow up our bundle regularly by packing two instances of arbitrarily libraries like lodash, mobx and such.

I hope that helps! I didn't try yarn workspaces yet btw, which might be interesting to try out as they promise to solve these kind of problems

@mweststrate
Copy link
Member Author

N.b. to prove point 4, after running yarn install my node_modules look like this:

➜  mobx-multiple-instance-issue-sample git:(master) ✗ ls -x -1 -R | grep mobx: 
./client/node_modules/mobx:
./client/node_modules/my-shared/node_modules/mobx:
./shared/node_modules/mobx:

Npm yielded the same output for me, but that was with npm 3

@johnnyreilly
Copy link

Thanks for that @mweststrate - my own setup is rather more vanilla than the setup shared here.

However the problem persists regardless of yarn / npm. I'll try and take the boilerplate TypeScript mobx project and replicate the issue there.

For now we're just ignoring the warning. Should we be worried? Things seem to work.....

@johnnyreilly
Copy link

Ps my own project is client only for clarity

@mweststrate
Copy link
Member Author

For now we're just ignoring the warning. Should we be worried? Things seem to work.....

Yes, the different libraries won't track each other observables, so that might mean that mobx-react will react only to half of your observables (the ones created by the instance it resolved it's own mobx dep to)

Besides that the lib will be bundled twice ;-)

@mweststrate
Copy link
Member Author

@danieldunderfelt it is actually a bug in TS-server afaik, so will solved in newer TS versions for any IDE

@breakds
Copy link

breakds commented Dec 30, 2017

If the shared library itself is a local module (yarn add file:../../../my-shared-module), the shared module itself may have node_modules/mobx. Even if all dependencies such as mobx and mobx-react are marked as peer and dev dependencies, the node_module/mobx will be included with that yarn add.

Therefore in my main project there will be 2 mobx:

$ ls -x -1 -R | grep mobx:
./node_modules/mobx
./node_modules/my-shared-module/node_modules/mobx

The only (hacky) solution I found is to manually remove the node_modules directory in the shared module directory after a build ...

@danieldunderfelt
Copy link

@mweststrate oh, good to know! WebStorm does the same for styled-components 🙄 Thanks!

@alexnofoget
Copy link

I solved this problem. I used my own module through npm link localy. Therefore creating two instances of mobx. When I moved to remote repo my module, I got one instance

@mweststrate
Copy link
Member Author

mweststrate commented Feb 28, 2018 via email

@mallikvarma
Copy link

Yes I have gone through above 2 examples and followed the same. To be honest its not easy for the developer to get mobx-react version 4 working. Annoyed, I have moved to 3.0 and that resolved the issue.

@mweststrate
Copy link
Member Author

mweststrate commented Mar 1, 2018 via email

@mallikvarma
Copy link

Sorry, I moved to mobx 3.0.0 and mobx-react is 4.4.2

@mweststrate
Copy link
Member Author

mweststrate commented Mar 1, 2018 via email

@mallikvarma
Copy link

I was using mobx 3.6.1

@pedsmoreira
Copy link

pedsmoreira commented Mar 9, 2018

In case someone encounters the same error:

I have a package in one project and a app created with create-react-app on another and I wanted to use my package on my create-react-app demo. After struggling for a while I found the response in this issue and I wanted to share how to make the linking work on create-react-app + react-app-rewired:

config-overrides.js

const path = require('path');
const rewireMobX = require('react-app-rewire-mobx');

module.exports = function override(config, env) {
  config = rewireMobX(config, env);
  config.resolve.alias = {
    mobx: path.resolve(__dirname, 'node_modules/mobx')
  };
  return config;
};

@nrigaudiere
Copy link

nrigaudiere commented Mar 14, 2018

Hi everyone,

I am currently migrating from PHP/Jquery to ReactJS/MobX and I have one MobX store per PhP page as I'm currently migrating.

So I was having this issue but it was just set as a warning but I've migrated to [email protected] and now it is shown as an Error :/

mobx.module.js:2625 Uncaught Error: [mobx] There are multiple mobx instances active. This might lead to unexpected results. See https://github.com/mobxjs/mobx/issues/1082 for details.
    at invariant (mobx.module.js:2625)
    at fail$1 (mobx.module.js:2620)
    at mobx.module.js:2850

Would someone have an idea on how to handle this?

Thank you very much!

@mweststrate
Copy link
Member Author

mweststrate commented Mar 14, 2018 via email

@ddbradshaw
Copy link

ddbradshaw commented Mar 27, 2018

FWIW - After updating my version on mobx-state-tree, I started receiving this error. I needed to then also update my version of mobx such that the version of mobx was higher than the stated version of the peer dependency in mobx-state-tree, otherwise webpack includes two different versions of mobx in the bundle and this exception is thrown.

@felixzxk
Copy link

felixzxk commented Apr 3, 2018

my react-native project, downgrade mobx to 3.6.2, mobx-react to 4.4.2

@abelkbil
Copy link

Dashboard.test.ts

import DashboardStore from '../Stores/DashboardStore';

describe('DashboardStore', () => {
    let dashboardStore;
    beforeEach(() => {
        dashboardStore = DashboardStore;
    });
    // tslint:disable-next-line:no-console
    console.log(dashboardStore);
});
 FAIL  src\__tests__\DashboardStore.test.ts
  ? Test suite failed to run

    TypeError: Cannot create property '__mobxInstanceCount' on boolean 'true'

      at Object.<anonymous> (node_modules/mobx/lib/mobx.js:2620:38)
      at Object.<anonymous> (src/Stores/DashboardStore.ts:9:14)
      at Object.<anonymous> (src/__tests__/DashboardStore.test.ts:3:24)

Test Suites: 1 failed, 1 total

mobx.js:2620 mentions about global multiple instance.

DashboardStore.ts

1 import { observable, action } from 'mobx';
2 import * as _ from 'lodash';
3 import agent from '../agent';
4 import { DragableItemModel, WidgetModel } from '../Model/Widget';
5 import widgetsListStore from './WidgetsListStore';
6 import timeSettingsStore from './TimeSetingsStore';
7 import { LayoutItem } from '../Model/LayoutItem';
8 import { MOMDatasource } from '../Services/MOMService';
9 import { DashboardModel } from '../Model/Dashboard';
10 import { DashboardService } from '../Services/DashboardService';
11 export class DashboardStore  {
"jest": {
    "collectCoverageFrom": [
      "src/**/*.{js,jsx,ts,tsx}"
    ],
    "setupFiles": [
      "<rootDir>/config/polyfills.js"
    ],
    "testMatch": [
      "<rootDir>/src/**/__tests__/**/*.ts?(x)",
      "<rootDir>/src/**/?(*.)(spec|test).ts?(x)"
    ],
    "testEnvironment": "node",
    "testURL": "http://localhost",
    "transform": {
      "^.+\\.tsx?$": "<rootDir>/config/jest/typescriptTransform.js",
      "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
      "^(?!.*\\.(js|jsx|mjs|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
    },
    "transformIgnorePatterns": [
      "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|ts|tsx)$"
    ],
    "moduleNameMapper": {
      "^react-native$": "react-native-web"
    },
    "moduleFileExtensions": [
      "mjs",
      "web.ts",
      "ts",
      "web.tsx",
      "tsx",
      "web.js",
      "js",
      "web.jsx",
      "jsx",
      "json",
      "node"
    ],
    "globals": {
      "ts-jest": {
        "tsConfigFile": "tsconfig.test.json"
      },
      "window": true
    }
  }

My request is provide some hind on how to unit test a component and store.
Thanks you.

@e41q
Copy link

e41q commented Apr 17, 2018

After upgrade webpack to 4 version, I'm stucked with this problem.
At webpack 3 we use CommonsChunkPlugin, but now it is deprecated.

Now I'm try configure splitChunksPlugin:

{
 module: {},
 optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendor",
          chunks: "all"
        }
      }
    }
  }
}

But anyway I'm see that error.
I'm try manually set alias as described here

alias: { mobx: path.resolve(__dirname, 'node_modules/mobx') })

But that didn't help.

And in my project, I have separate version of mobx, but only for storybook/react. And if I remove them, that didn't help.

ls -x -1 -R | grep mobx:
./node_modules/mobx:

@mweststrate
Copy link
Member Author

Locking this conversation as adding more examples only makes this thread more uncomprehensible.

Please read those comments carefully, a few times.

And only then open an issue, including a small reproduction.

@mobxjs mobxjs locked and limited conversation to collaborators Apr 17, 2018
@mweststrate
Copy link
Member Author

In MobX 4.4 / 5.1 the behavior has changed and sharing is now the default. Please note that nonetheless one should make sure a module is included only once, but this is now considered the responsibility of the consumer

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests