Migrating from MobX 4/5 {π}
MobX 6 is quite different from MobX 5. This pages covers a migration guide from MobX 4 and 5 to 6, and an extensive list of all the changes.
For a better understanding, check out the MobX 6.0 CHANGELOG.
β οΈ Warning: Depending on factors like the size and complexity of your code base, your MobX usage patterns, and the quality of your automated tests, this migration guide might take you anywhere between an hour and a couple of days. Please refrain from upgrading if you don't trust your Continuous Integration or QA / test procedures enough to pick up any unexpected breakages. Unexpected behavioral changes might be caused by changes in MobX itself or the changes needed to your Babel / TypeScript build configuration. β οΈ
Getting started
- Update
mobx
to the latest version of MobX 4/5 and solve any deprecation messages. - Update
mobx
to version 6. - If you are upgrading from MobX 4, and you will need to support Internet Explorer / React Native without proxies, call
import { configure } from "mobx"; configure({ useProxies: "never" })
at the initialization of your application, to back-out of the Proxy implementation. Check out the Proxy Support section for more details. - For babel users:
- If you are using Babel and have class-properties enabled, disable the legacy loose field support:
["@babel/plugin-proposal-class-properties", { "loose": false }]
- (Optional) In MobX 6 decorators have become opt-in. If you no longer wish to use decorators, remove
plugin-proposal-decorators
from your babel configuration and dependencies. Check out the Enabling decorators {π} section for more details.
- If you are using Babel and have class-properties enabled, disable the legacy loose field support:
- For Typescript users:
- Add the flag
"useDefineForClassFields": true
to your compiler config. - (Optional) In MobX 6 decorators have become opt-in. If you no longer wish to use decorators, remove / disable the
experimentalDecorators
configuration from your TypeScript config. Check out the Enabling decorators {π} section for more details.
- Add the flag
- The MobX default configuration has become more strict. We recommend to adopt the new defaults after completing the upgrade, check out the Configuration {π} section. During migration, we recommend to configure MobX in the same way as it would be in v4/v5 out of the box:
import {configure} from "mobx"; configure({ enforceActions: "never" });
. After finishing the entire migration process and validating that your project works as expected, consider enabling the flagscomputedRequiresReaction
,reactionRequiresObservable
andobservableRequiresReaction
andenforceActions: "observed"
to write more idiomatic MobX code.
makeObservable
Upgrading classes to use Due to standardized JavaScript limitations in how class fields are constructed, it is no longer possible for MobX to alter the behavior of class fields by means of decorators or the decorate
utility. Instead, fields have to be made observable by the constructor
. This can be done in three different ways:
- Remove all decorators and call
makeObservable
in theconstructor
and explicitly define which field should be made observable using which decorator. For example:makeObservable(this, { count: observable, tick: action, elapsedTime: computed })
(note that the second argument corresponds to what would be passed todecorate
). This is the recommended approach if you want to drop decorators in your code base, and the project isn't yet too big. - Leave all the decorators and call
makeObservable(this)
in theconstructor
. This will pick up the metadata generated by the decorators. This is the recommended way if you want to limit the impact of a MobX 6 migration. - Remove decorators and use
makeAutoObservable(this)
in the classconstructor
's.
Check out makeObservable / makeAutoObservable for more details.
Some specifics to note:
- Using
makeObservable
/makeAutoObservable
needs to be done in every class definition that declares MobX based members. So if a sub-class and super-class both introduce observable members, they will both have to callmakeObservable
. makeAutoObservable
will mark methods using a new decoratorautoAction
, that will applyaction
only if it is not in a derivation context. This makes it safe to call automatically decorated methods also from computed properties.
Migrating a large code base with lots of classes might be daunting. But no worries, there is a code-mod available that will automate the above process!!
mobx-undecorate
codemod
Upgrading your code with the If you are an existing MobX user you have code that uses a lot of decorators, or the equivalent calls to decorate
.
The mobx-undecorate
package provides a codemod that can automatically update your code to be conformant to MobX 6. There is no need to install it; instead you download and execute it using the npx
tool which you do need to install if you haven't already.
To get rid of all uses of MobX decorators and replace them with the equivalent makeObservable
calls, go to the directory that contains your source code and run:
npx mobx-undecorate
MobX will continue to support decorators -- so if you want to retain them
and only introduce makeObservable(this)
where required, you can use the --keepDecorators
option:
npx mobx-undecorate --keepDecorators
See documentation for more options.
mobx-undecorate
Limitations of The mobx-undecorate
command has to introduce a constructor in classes that do not yet have one. If base class of the constructor expects arguments, the codemod cannot introduce these arguments for the subclass being upgraded, and the super
call won't pass them either. You have to fix these manually.
The tool will generate a // TODO: [mobx-undecorate]
comment in these cases.
We do have a special case for React class components to do the right thing and
pass along props
to the superclass.
Functions are auto-converted
Functions that become part of a deep observable structure are automatically converted to autoAction
or to flow
if it's a generator function. See inference rules for details.
This means that the original function reference is not preserved - in the same spirit as the original array/object/set/map reference is lost when converted to observable. This can be surprising in some situations.
If this behavior is not desired use observable.shallow
/ observable.ref
/ false
/ deep: flase
to prevent the conversion process or make sure the function is already an action
as shown in the issue.