Skip to content
Open

MobX 7 #4671

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
a786288
Set up bundle check
kubk Jun 9, 2026
1af94f9
Remove legacy MobX runtime support
kubk Jun 9, 2026
8a41cac
Fold lite React bindings into mobx-react
kubk Jun 9, 2026
21e3add
Remove mobx-react-lite workspace
kubk Jun 9, 2026
bded89d
Update docs for MobX 7
kubk Jun 9, 2026
06f55fb
Restore actions docs from main
kubk Jun 9, 2026
79a4fce
Clean up MobX 7 docs wording
kubk Jun 9, 2026
7a468f7
Trim Proxy support docs wording
kubk Jun 9, 2026
fda8075
Refine installation guide wording
kubk Jun 9, 2026
846b1d5
Remove redundant decorator cleanup note
kubk Jun 9, 2026
1385f47
cleanup
kubk Jun 10, 2026
5250ff6
Tighten MobX 7 migration guide
kubk Jun 10, 2026
1a08071
cleanup
kubk Jun 10, 2026
e85070e
cleanup
kubk Jun 10, 2026
303629b
cleanup
kubk Jun 10, 2026
c015884
cleanup
kubk Jun 10, 2026
fc1d127
cleanup
kubk Jun 10, 2026
7fa2aad
stable formatting on installation
kubk Jun 10, 2026
0157171
Restore migrated mobx-react-lite tests
kubk Jun 10, 2026
9a4fb7c
Remove batching
kubk Jun 10, 2026
92fd545
Add react batching tests
kubk Jun 10, 2026
ae82d15
cleanup test
kubk Jun 10, 2026
65ac62e
Remove useless alias
kubk Jun 10, 2026
37d6fe1
better changeset
kubk Jun 10, 2026
fd999c2
better changeset [skip ci]
kubk Jun 10, 2026
d4e727e
wording [skip ci]
kubk Jun 10, 2026
b92c9c8
Centralize mobx-react test cleanup
kubk Jun 10, 2026
73cb627
Switch to plain rollup
kubk Jun 11, 2026
80e06be
Bring back mobx-react-lite
kubk Jun 11, 2026
b247803
cleanup
kubk Jun 11, 2026
59cfba0
Fix observer test type errors
kubk Jun 11, 2026
cc5edca
migration 7 cleanup [skip ci]
kubk Jun 11, 2026
f1eda94
cleanu p
kubk Jun 11, 2026
7770107
Fix TS
kubk Jun 11, 2026
581e837
clean up [skip ci]
kubk Jun 11, 2026
dc2ad2e
f [skip ci]
kubk Jun 11, 2026
e697863
cross update required deps
kubk Jun 11, 2026
335bdce
f
kubk Jun 11, 2026
42216aa
shorten diff
kubk Jun 11, 2026
8a69572
cleanup
kubk Jun 11, 2026
9ff6ee4
Potential fix for pull request finding
kubk Jun 12, 2026
e794298
Fix mobx-react test source import
kubk Jun 12, 2026
b4dd4ef
Fix copilot warning
kubk Jun 15, 2026
484a506
Merge branch 'main' into mobx-v7
kubk Jun 15, 2026
334d857
Shrink dev deps
kubk Jun 16, 2026
51c0cda
Simplify test
kubk Jun 17, 2026
4f52f08
don't deep-import mobx internals in size comparison script
kubk Jun 17, 2026
2931a64
Fix MobX 7 peer dependency ranges
kubk Jun 18, 2026
d786df9
Update trace note
kubk Jun 18, 2026
efa2640
wording [skip ci]
kubk Jun 18, 2026
d93ac47
Simplify decorator types / factories
kubk Jun 18, 2026
494d28d
Remove outdated errors
kubk Jun 18, 2026
f74e920
Correct cross deps + drop perf script param
kubk Jun 18, 2026
493ab28
Version packages for MobX 7
kubk Jun 18, 2026
05c7f4d
Remove the fallback for the old makeObservable usage without the seco…
kubk Jun 18, 2026
4eca3b9
Fix alias
kubk Jun 18, 2026
feb47f4
Revert "Version packages for MobX 7"
kubk Jun 18, 2026
2d31b3f
Keep prerelease dev deps installable
kubk Jun 18, 2026
9389778
Address become observed feedback
kubk Jun 18, 2026
f582627
Hoist dev deps
kubk Jun 19, 2026
07c4d22
Unify rollup config creation
kubk Jun 19, 2026
db9e001
All configs to .mjs
kubk Jun 19, 2026
4e2267e
Clean up build
kubk Jun 19, 2026
8cdd873
Even smaller bundle size
kubk Jun 19, 2026
1b606a3
Move size check to the root
kubk Jun 19, 2026
206d136
Fix check size CI
kubk Jun 19, 2026
648a343
resurrect benchmarks in tests
kubk Jun 19, 2026
e08d360
Further reduce bundlesize
kubk Jun 23, 2026
0c95480
Remove namespaced annotations / comparers
kubk Jun 24, 2026
5f22cd4
Merge remote-tracking branch 'origin/main' into mobx-v7
kubk Jun 24, 2026
7a8746f
reflect new bundlesize improvements
kubk Jun 24, 2026
f18b754
rename comparer* -> compare*
kubk Jun 24, 2026
a0c3035
upd changeset
kubk Jun 24, 2026
021200b
clarify changeset [skip ci]
kubk Jun 24, 2026
dbd321f
clean up
kubk Jun 24, 2026
17b89a8
Use assign alias to trim down the bundle
kubk Jun 24, 2026
76f814e
Shorten errors & flow names in prod
kubk Jun 24, 2026
8b1393c
drop unreachable branch
kubk Jun 24, 2026
8eb4e1f
restore formatting
kubk Jun 24, 2026
3b1a872
http isn't required
kubk Jul 1, 2026
fbbb0f1
Reduce tree-shaken Mobx size to ~10kb gzip
kubk Jul 10, 2026
30eef8f
Merge branch 'main' into mobx-v7
kubk Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .changeset/mobx-7-react-10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
"mobx": major
"mobx-react-lite": major
"mobx-react": major
---

Release MobX 7, mobx-react-lite 5, and mobx-react 10.

Bundle sizes are down: ESM prod 17.02 KiB gzip -> 13.96 KiB gzip; a minimal tree-shaken example is 10.32 KiB gzip now.

It removes long-deprecated compatibility paths and keeps the React bindings split between `mobx-react-lite` for function components and `mobx-react` for class-component support.

## MobX 7

MobX 7 is a cleanup release focused on the modern runtime and decorator model.

- MobX now always uses Proxy-backed observable objects and arrays. The ES5/non-proxy fallback has been removed.
- `configure({ useProxies: ... })` is no longer supported.
- `{ proxy: false }` options for `observable`, `observable.object`, and `observable.array` are no longer supported.
- Legacy decorators are no longer supported.
- Namespaced annotation and comparer properties now use named exports to reduce bundle size:

| Removed API | Replacement |
| --------------------- | ------------------- |
| `observable.ref` | `observableRef` |
| `observable.shallow` | `observableShallow` |
| `observable.deep` | `observableDeep` |
| `observable.struct` | `observableStruct` |
| `computed.struct` | `computedStruct` |
| `action.bound` | `actionBound` |
| `flow.bound` | `flowBound` |
| `comparer.identity` | `compareIdentity` |
| `comparer.default` | `compareDefault` |
| `comparer.structural` | `compareStructural` |
| `comparer.shallow` | `compareShallow` |

- The public `trace` API and its related runtime support have been removed. Use `toJS`, `getDependencyTree`, `getObserverTree`, `spy` or `mobx-log` package for debugging.

## mobx-react-lite 5 and mobx-react 10

mobx-react-lite 5 and mobx-react 10 require MobX 7 and React 18 or later.

`mobx-react-lite` remains the function-component package. `mobx-react` remains a thin wrapper around `mobx-react-lite` that adds class component and Stage 3 `@observer` class decorator support.

- Keep function-component imports on `mobx-react-lite` if you do not need class component support.
- Use `mobx-react` when you need class components or `@observer` class decorators.
- `mobx-react-lite` supports function components and `forwardRef`; `mobx-react` delegates function components to `mobx-react-lite` and handles classes itself.
- Remove React batching imports, including the stale React Native batching deep import. React 18+ renderers handle batching.

The recommended public React binding surface for both packages is:

- `observer`
- `Observer`
- `useLocalObservable`
- `enableStaticRendering`
- `isUsingStaticRendering`

The following APIs have been removed from the React binding packages:

- `Provider`, `inject`, and `MobXProviderContext`; use `React.createContext` directly.
- `disposeOnUnmount`; dispose reactions in `componentWillUnmount` or return cleanup functions from `useEffect`.
- `PropTypes`; use TypeScript or the regular `prop-types` package.
- `useObserver`; wrap components with `observer` or use `<Observer>`.
- `useLocalStore`; use `useLocalObservable`.
- `useAsObservableSource`; synchronize values from props into local observable state explicitly.
- `useStaticRendering`; use `enableStaticRendering`.
- `observerBatching`, `isObserverBatched`, `batchingForReactDom`, `batchingOptOut`, and `batchingForReactNative`; remove these imports because React 18+ renderers handle batching.
- Deprecated `observer(fn, { forwardRef: true })`; pass an already-created `React.forwardRef(...)` component to `observer` instead.
- Legacy function-component `contextTypes` handling.
4 changes: 2 additions & 2 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ jobs:
- name: Test
run: npm test -- -i

- name: Test size
run: npm run test:size
- name: Check size
run: npm run check-size

- name: Test performance
run: npm -w mobx run test:performance
52 changes: 0 additions & 52 deletions docs/analyzing-reactivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,6 @@ hide_title: true

# Analyzing reactivity {🚀}

# Using `trace` for debugging

Trace is a small utility that helps you find out why your computed values, reactions or components are re-evaluating.

It can be used by simply importing `import { trace } from "mobx"`, and then putting it inside a reaction or computed value.
It will print why it is re-evaluating the current derivation.

Optionally it is possible to automatically enter the debugger by passing `true` as the last argument.
This way the exact mutation that causes the reaction to re-run will still be in stack, usually ~8 stack frames up. See the image below.

In debugger mode, the debug information will also reveal the full derivation tree that is affecting the current computation / reaction.

![trace](assets/trace-tips2.png)

![trace](assets/trace.gif)

## Live examples

Simple [CodeSandbox `trace` example](https://codesandbox.io/s/trace-dnhbz?file=/src/index.js:309-338).

[Here's a deployed example](https://csb-nr58ylyn4m-hontnuliaa.now.sh/) for exploring the stack.
Make sure to play with the chrome debugger's blackbox feature!

## Usage examples

There are different ways of calling `trace()`, some examples:

```javascript
import { observer } from "mobx-react"
import { trace } from "mobx"

const MyComponent = observer(() => {
trace(true) // Enter the debugger whenever an observable value causes this component to re-run.
return <div>{this.props.user.name}</name>
})
```

Enable trace by using the `reaction` argument of a reaction / autorun:

```javascript
mobx.autorun("logger", reaction => {
reaction.trace()
console.log(user.fullname)
})
```

Pass in the property name of a computed property:

```javascript
trace(user, "fullname")
```

# Introspection APIs

The following APIs might come in handy if you want to inspect the internal state of MobX while debugging, or want to build cool tools on top of MobX.
Expand Down
15 changes: 4 additions & 11 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ _Making things observable._

### `makeObservable`

Usage: `makeObservable(target, annotations?, options?)`
Usage: `makeObservable(target, annotations, options?)`
<small>(<b>[further information](observable-state.md#makeobservable)</b>)</small>

Properties, entire objects, arrays, Maps and Sets can all be made observable.
Expand Down Expand Up @@ -89,7 +89,7 @@ If the values in the array should not be turned into observables automatically,

Creates a new observable [ES6 Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) based on the provided `initialMap`.
They are very useful if you don't want to react just to the change of a specific entry, but also to their addition and removal.
Creating observable Maps is the recommended approach for creating dynamically keyed collections if you don't have [enabled Proxies](configuration.md#proxy-support).
Creating observable Maps is the recommended approach for creating dynamically keyed collections.

Besides all the language built-in Map functions, the following goodies are available on observable Maps as well:

Expand Down Expand Up @@ -216,7 +216,7 @@ Creates an observable value that is derived from other observables, but won't be

## React integration

_From the `mobx-react` / `mobx-react-lite` packages._
_From `mobx-react-lite` for function components, or `mobx-react` for class component support._

### `observer`

Expand Down Expand Up @@ -345,7 +345,7 @@ Use it to change how MobX behaves as a whole.

## Collection utilities {🚀}

_They enable manipulating observable arrays, objects and Maps with the same generic API. This can be useful in [environments without `Proxy` support](configuration.md#limitations-without-proxy-support), but is otherwise typically not needed._
_They enable manipulating observable arrays, objects and Maps with the same generic API._

### `values`

Expand Down Expand Up @@ -462,13 +462,6 @@ Is this a boxed computed value, created using `computed(() => expr)`?

Is this a computed property?

### `trace`

{🚀} Usage: `trace()`, `trace(true)` _(enter debugger)_ or `trace(object, propertyName, enterDebugger?)`
<small>(<b>[further information](analyzing-reactivity.md)</b>)</small>

Should be used inside an observer, reaction or computed value. Logs when the value is invalidated, or sets the debugger breakpoint if called with _true_.

### `spy`

{🚀} Usage: `spy(eventListener)`
Expand Down
12 changes: 11 additions & 1 deletion docs/assets/getting-started-assets/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ function runCodeHelper(code) {
window.autorun = mobx.autorun
window.computed = mobx.computed
window.action = mobx.action
window.observer = mobxReactLite.observer
window.observer = mobxReact.observer
window.makeObservable = mobx.makeObservable
window.makeAutoObservable = mobx.makeAutoObservable
window.renderReactApp = renderReactApp

var globalEval = eval // global scope trick, See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

Expand All @@ -41,6 +42,15 @@ function runCodeHelper(code) {
}
}

function renderReactApp(element) {
var container = document.getElementById("reactjs-app")
if (!window.__cachedReactRoot) {
// Reuse one React 18 root across repeated "Run code" clicks.
window.__cachedReactRoot = ReactDOM.createRoot(container)
}
window.__cachedReactRoot.render(element)
}

function runCode(ids) {
$(ids.join(",")).each(function(i, elem) {
clearConsole()
Expand Down
Binary file removed docs/assets/trace-tips2.png
Binary file not shown.
Binary file removed docs/assets/trace.gif
Binary file not shown.
4 changes: 1 addition & 3 deletions docs/collection-utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ hide_title: true
# Collection utilities {🚀}

They enable manipulating observable arrays, objects and Maps with the same generic API.
These APIs are fully reactive, which means that even [without `Proxy` support](configuration.md#limitations-without-proxy-support) new property declarations can be detected by MobX if `set` is used to add them, and `values` or `keys` are used to iterate over them.
These APIs are fully reactive and can track keys, values and entries without depending on the concrete collection type.

Another benefit of `values`, `keys` and `entries` is that they return arrays rather than iterators, which makes it possible to, for example, immediately call `.map(fn)` on the results.

Expand All @@ -28,8 +28,6 @@ Mutation:
- `has(collection, key)` returns _true_ if the collection has the specified _observable_ property.
- `get(collection, key)` returns the child under the specified key.

If you use the access APIs in an environment without `Proxy` support, then also use the mutation APIs so they can detect the changes.

```javascript
import { autorun, get, set, observable, values } from "mobx"

Expand Down
20 changes: 7 additions & 13 deletions docs/computeds-with-args.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ and the application supports multi-selection.
How can we implement a derivation like `store.isSelected(item.id)`?

```javascript
import * as React from 'react'
import { observer } from 'mobx-react-lite'
import * as React from "react"
import { observer } from "mobx-react-lite"

const Item = observer(({ item, store }) => (
<div className={store.isSelected(item.id) ? "selected" : ""}>
{item.title}
</div>
<div className={store.isSelected(item.id) ? "selected" : ""}>{item.title}</div>
))
```

Expand All @@ -46,17 +44,13 @@ This is a worst-case example. In general, it is completely fine to have unmarked
This is a more efficient implementation compared to the original.

```javascript
import * as React from 'react'
import { computed } from 'mobx'
import { observer } from 'mobx-react-lite'
import * as React from "react"
import { computed } from "mobx"
import { observer } from "mobx-react-lite"

const Item = observer(({ item, store }) => {
const isSelected = computed(() => store.isSelected(item.id)).get()
return (
<div className={isSelected ? "selected" : ""}>
{item.title}
</div>
)
return <div className={isSelected ? "selected" : ""}>{item.title}</div>
})
```

Expand Down
16 changes: 8 additions & 8 deletions docs/computeds.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,22 +214,22 @@ This string is used as a debug name in the [Spy event listeners](analyzing-react

### `equals`

Set to `comparer.default` by default. It acts as a comparison function for comparing the previous value with the next value. If this function considers the values to be equal, then the observers will not be re-evaluated.
Set to `compareDefault` by default. It acts as a comparison function for comparing the previous value with the next value. If this function considers the values to be equal, then the observers will not be re-evaluated.

This is useful when working with structural data and types from other libraries. For example, a computed [moment](https://momentjs.com/) instance could use `(a, b) => a.isSame(b)`. `comparer.structural` and `comparer.shallow` come in handy if you want to use structural / shallow comparison to determine whether the new value is different from the previous value, and as a result notify its observers.
This is useful when working with structural data and types from other libraries. For example, a computed [moment](https://momentjs.com/) instance could use `(a, b) => a.isSame(b)`. `compareStructural` and `compareShallow` come in handy if you want to use structural / shallow comparison to determine whether the new value is different from the previous value, and as a result notify its observers.

Check out the [`computed.struct`](#computed-struct) section above.

#### Built-in comparers

MobX provides four built-in `comparer` methods which should cover most needs of the `equals` option of `computed`:
MobX provides four built-in comparison functions which should cover most needs of the `equals` option of `computed`:

- `comparer.identity` uses the identity (`===`) operator to determine if two values are the same.
- `comparer.default` is the same as `comparer.identity`, but also considers `NaN` to be equal to `NaN`.
- `comparer.structural` performs deep structural comparison to determine if two values are the same.
- `comparer.shallow` performs shallow structural comparison to determine if two values are the same.
- `compareIdentity` uses the identity (`===`) operator to determine if two values are the same.
- `compareDefault` uses `Object.is` to determine if two values are the same.
- `compareStructural` performs deep structural comparison to determine if two values are the same.
- `compareShallow` performs shallow structural comparison to determine if two values are the same.

You can import `comparer` from `mobx` to access these methods. They can be used for `reaction` as well.
You can import these functions from `mobx`. They can be used for `reaction` as well.

### `requiresReaction`

Expand Down
34 changes: 4 additions & 30 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,11 @@ Most configuration options can be set by using the `configure` method.

## Proxy support

By default, MobX uses proxies to make arrays and plain objects observable. Proxies provide the best performance and most consistent behavior across environments.
However, if you are targeting an environment that doesn't support proxies, proxy support has to be disabled.
Most notably this is the case when targeting Internet Explorer or React Native without using the Hermes engine.
MobX requires [`Proxy` support](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) to make arrays and plain objects observable.
Proxies provide the best performance and most consistent behavior across environments.
Comment thread
kubk marked this conversation as resolved.

Proxy support can be disabled by using `configure`:

```typescript
import { configure } from "mobx"

configure({
useProxies: "never"
})
```

Accepted values for the `useProxies` configuration are:
Comment thread
kubk marked this conversation as resolved.

- `"always"` (**default**): MobX expects to run only in environments with [`Proxy` support](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) and it will error if such an environment is not available.
- `"never"`: Proxies are not used and MobX falls back on non-proxy alternatives. This is compatible with all ES5 environments, but causes various [limitations](#limitations-without-proxy-support).
- `"ifavailable"` (experimental): Proxies are used if they are available, and otherwise MobX falls back to non-proxy alternatives. The benefit of this mode is that MobX will try to warn if APIs or language features that wouldn't work in ES5 environments are used, triggering errors when hitting an ES5 limitation running on a modern environment.

**Note:** before MobX 6, one had to pick either MobX 4 for older engines, or MobX 5 for new engines. However, MobX 6 supports both, although polyfills for certain APIs like Map will be required when targetting older JavaScript engines.
Proxies cannot be polyfilled. Even though polyfills do exist, they don't support the full spec and are unsuitable for MobX. Don't use them.

### Limitations without Proxy support

1. Observable arrays are not real arrays, so they won't pass the `Array.isArray()` check. The practical consequence is that you often need to `.slice()` the array first (to get a shallow copy of the real array) before passing it to third party libraries. For example, concatenating observable arrays doesn't work as expected, so `.slice()` them first.
2. Adding or deleting properties of existing observable plain objects after creation is not automatically picked up. If you intend to use objects as index based lookup maps, in other words, as dynamic collections of things, use observable Maps instead.

It is possible to dynamically add properties to objects, and detect their additions, even when Proxies aren't enabled.
This can be achieved by using the [Collection utilities {🚀}](collection-utilities.md). Make sure that (new) properties are set using the `set` utility, and that the objects are iterated using one of the `values` / `keys` or `entries` utilities, rather than the built-in JavaScript mechanisms.
But, since this is really easy to forget, we instead recommend using observable Maps if possible.
MobX 7 does not include the older ES5 fallback implementation and no longer supports `configure({ useProxies })`.
Use MobX 6 if you need to support environments without Proxy support, such as Internet Explorer or older React Native versions.

## Decorator support

Expand Down
Loading