diff --git a/.changeset/fix-minified-error-link.md b/.changeset/fix-minified-error-link.md
deleted file mode 100644
index ac68f01ee4..0000000000
--- a/.changeset/fix-minified-error-link.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"mobx": patch
----
-
-Shorten minified error URL to reduce production bundle size.
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
index 0c49260cbe..85f6cb9adb 100644
--- a/.github/workflows/build_and_test.yml
+++ b/.github/workflows/build_and_test.yml
@@ -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
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7c85faac05..aa4a160879 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
+ workflow_dispatch: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
diff --git a/docs/analyzing-reactivity.md b/docs/analyzing-reactivity.md
index 80ad31378a..fb230add38 100644
--- a/docs/analyzing-reactivity.md
+++ b/docs/analyzing-reactivity.md
@@ -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.
-
-
-
-
-
-## 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
{this.props.user.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.
diff --git a/docs/api.md b/docs/api.md
index 4db83b102a..9f3fc32460 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -25,7 +25,7 @@ _Making things observable._
### `makeObservable`
-Usage: `makeObservable(target, annotations?, options?)`
+Usage: `makeObservable(target, annotations, options?)`
([further information](observable-state.md#makeobservable))
Properties, entire objects, arrays, Maps and Sets can all be made observable.
@@ -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:
@@ -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`
@@ -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`
@@ -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?)`
-([further information](analyzing-reactivity.md))
-
-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)`
diff --git a/docs/assets/getting-started-assets/script.js b/docs/assets/getting-started-assets/script.js
index 1d51fd9ce7..a4ddfe6db4 100755
--- a/docs/assets/getting-started-assets/script.js
+++ b/docs/assets/getting-started-assets/script.js
@@ -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
@@ -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()
diff --git a/docs/assets/trace-tips2.png b/docs/assets/trace-tips2.png
deleted file mode 100644
index 56f4c17d20..0000000000
Binary files a/docs/assets/trace-tips2.png and /dev/null differ
diff --git a/docs/assets/trace.gif b/docs/assets/trace.gif
deleted file mode 100644
index 35f171861c..0000000000
Binary files a/docs/assets/trace.gif and /dev/null differ
diff --git a/docs/collection-utilities.md b/docs/collection-utilities.md
index f97bf9ae9a..d4bf7894be 100644
--- a/docs/collection-utilities.md
+++ b/docs/collection-utilities.md
@@ -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.
@@ -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"
diff --git a/docs/computeds-with-args.md b/docs/computeds-with-args.md
index c217b28cc1..e848f48fe1 100644
--- a/docs/computeds-with-args.md
+++ b/docs/computeds-with-args.md
@@ -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 }) => (
-
- {item.title}
-
+
{item.title}
))
```
@@ -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 (
-
- {item.title}
-
- )
+ return
{item.title}
})
```
diff --git a/docs/computeds.md b/docs/computeds.md
index 9e4f78a937..d0f2eb00fb 100644
--- a/docs/computeds.md
+++ b/docs/computeds.md
@@ -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`
diff --git a/docs/configuration.md b/docs/configuration.md
index 0b3486357b..7a4171406a 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -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.
-Proxy support can be disabled by using `configure`:
-
-```typescript
-import { configure } from "mobx"
-
-configure({
- useProxies: "never"
-})
-```
-
-Accepted values for the `useProxies` configuration are:
-
-- `"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
diff --git a/docs/enabling-decorators.md b/docs/enabling-decorators.md
index 29850a8523..98f3696d4e 100644
--- a/docs/enabling-decorators.md
+++ b/docs/enabling-decorators.md
@@ -90,47 +90,8 @@ class TodoList {
Notice the usage of the new `accessor` keyword when using `@observable`.
It is part of the 2022.3 spec and is required if you want to use modern decorators.
-Using legacy decorators
-
-We do not recommend codebases to use TypeScript / Babel legacy decorators since they well never become an official part of the language, but you can still use them. It does require a specific setup for transpilation:
-
-MobX before version 6 encouraged the use of legacy decorators and mark things as `observable`, `computed` and `action`.
-While MobX 6 recommends against using these decorators (and instead use either modern decorators or [`makeObservable` / `makeAutoObservable`](observable-state.md)), it is in the current major version still possible.
-Support for legacy decorators will be removed in MobX 7.
-
-```javascript
-import { makeObservable, observable, computed, action } from "mobx"
-
-class Todo {
- id = Math.random()
- @observable title = ""
- @observable finished = false
-
- constructor() {
- makeObservable(this)
- }
-
- @action
- toggle() {
- this.finished = !this.finished
- }
-}
-
-class TodoList {
- @observable todos = []
-
- @computed
- get unfinishedTodoCount() {
- return this.todos.filter(todo => !todo.finished).length
- }
-
- constructor() {
- makeObservable(this)
- }
-}
-```
-
-
+If you still need legacy TypeScript decorators, use MobX 6.
+MobX 7 requires migrating to modern decorators.
Migrating from legacy decorators
@@ -146,15 +107,13 @@ Please note that adding `accessor` to a class property will change it into `get`
Decorator changes / gotchas
-MobX' 2022.3 Decorators are very similar to the MobX 5 decorators, so usage is mostly the same, but there are some gotchas:
+MobX' 2022.3 decorators have some gotchas:
- `@observable accessor` decorators are _not_ enumerable. `accessor`s do not have a direct equivalent in the past - they're a new concept in the language. We've chosen to make them non-enumerable, non-own properties in order to better follow the spirit of the ES language and what `accessor` means.
The main cases for enumerability seem to have been around serialization and rest destructuring.
- Regarding serialization, implicitly serializing all properties probably isn't ideal in an OOP-world anyway, so this doesn't seem like a substantial issue (consider implementing `toJSON` or using `serializr` as possible alternatives)
- Addressing rest-destructuring, such is an anti-pattern in MobX - doing so would (likely unwantedly) touch all observables and make the observer overly-reactive).
-- `@action some_field = () => {}` was and is valid usage. However, inheritance is different between legacy decorators and modern decorators.
- - In legacy decorators, if superclass has a field decorated by `@action`, and subclass tries to override the same field, it will throw a `TypeError: Cannot redefine property`.
- - In modern decorators, if superclass has a field decorated by `@action`, and subclass tries to override the same field, it's allowed to override the field. However, the field on subclass is not an action unless it's also decorated with `@action` in subclass declaration.
+- `@action some_field = () => {}` is valid usage. If a superclass has a field decorated by `@action`, a subclass can override the field. However, the field on the subclass is not an action unless it is also decorated with `@action` in the subclass declaration.
diff --git a/docs/installation.md b/docs/installation.md
index b6bae03c20..e25769bc5d 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -8,18 +8,21 @@ hide_title: true
# Installation
-MobX works in any ES5 environment, which includes browsers and NodeJS.
+MobX works in browsers and Node.js environments that provide native `Proxy` support.
There are three types of React bindings:
-- [mobx-react-lite](https://github.com/mobxjs/mobx/tree/main/packages/mobx-react-lite). Utilities to manually apply observation
-- [mobx-react-observer](https://github.com/christianalfoni/mobx-react-observer). Babel/swc plugin to automatically apply observation to components
-- [mobx-react](https://github.com/mobxjs/mobx/tree/main/packages/mobx-react). Support for class components
-Append the appropriate bindings for your use case to the _Yarn_ or _NPM_ command below:
+- [mobx-react-lite](https://github.com/mobxjs/mobx/tree/main/packages/mobx-react-lite). Utilities to manually apply observation
+- [mobx-react-observer](https://github.com/christianalfoni/mobx-react-observer). Babel/swc plugin to automatically apply observation to components
+- [mobx-react](https://github.com/mobxjs/mobx/tree/main/packages/mobx-react). Support for class components
-**Yarn:** `yarn add mobx`
+Append the appropriate bindings for your use case to one of the commands below:
-**NPM:** `npm install --save mobx`
+**npm:** `npm install mobx`
+
+**pnpm:** `pnpm add mobx`
+
+**yarn:** `yarn add mobx`
**CDN:** https://cdnjs.com/libraries/mobx / https://unpkg.com/mobx/dist/mobx.umd.production.min.js
@@ -28,9 +31,7 @@ Append the appropriate bindings for your use case to the _Yarn_ or _NPM_ command
## MobX and Decorators
Based on your preference, MobX can be used with or without decorators.
-Both the legacy implementation and the standardised TC-39 version of decorators are currently supported.
See [enabling-decorators](enabling-decorators.md) for more details on how to enable them.
-Legacy decorator support will be removed in MobX 7, in favor of the standard.
## Use spec compliant transpilation for class properties
@@ -38,6 +39,7 @@ When using MobX with TypeScript or Babel, and you plan to use classes; make sure
- **TypeScript**: Set the compiler option `"useDefineForClassFields": true`.
- **Babel**: Make sure to use at least version 7.12, with the following configuration:
+
```json
{
// Babel < 7.13.0
@@ -50,25 +52,14 @@ When using MobX with TypeScript or Babel, and you plan to use classes; make sure
}
}
```
-For verification insert this piece of code at the beginning of your sources (eg. `index.js`)
-```javascript
-if (!new class { x }().hasOwnProperty('x')) throw new Error('Transpiler is not configured correctly');
-```
-## MobX on older JavaScript environments
-
-By default, MobX uses proxies for optimal performance and compatibility. However, on older JavaScript engines `Proxy` is not available (check out [Proxy support](https://compat-table.github.io/compat-table/es6/#test-Proxy)). Examples of such are Internet Explorer (before Edge), Node.js < 6, iOS < 10, Android before RN 0.59.
-
-In such cases, MobX can fallback to an ES5 compatible implementation which works almost identically, although there are a few [limitations without Proxy support](configuration.md#limitations-without-proxy-support). You will have to explicitly enable the fallback implementation by configuring [`useProxies`](configuration.md#proxy-support):
+For verification insert this piece of code at the beginning of your sources (eg. `index.js`)
+
```javascript
-import { configure } from "mobx"
-
-configure({ useProxies: "never" }) // Or "ifavailable".
+if (!new (class { x })().hasOwnProperty("x")) throw new Error("Transpiler is not configured correctly")
```
-This option will be removed in MobX 7.
-
## MobX on other frameworks / platforms
- [MobX.dart](https://mobx.netlify.app/): MobX for Flutter / Dart
diff --git a/docs/migrating-from-6-to-7.md b/docs/migrating-from-6-to-7.md
new file mode 100644
index 0000000000..02921f6c71
--- /dev/null
+++ b/docs/migrating-from-6-to-7.md
@@ -0,0 +1,295 @@
+---
+title: Migrating to MobX 7
+sidebar_label: Migrating to MobX 7 {🚀}
+hide_title: true
+---
+
+
+
+# Migrating to MobX 7 {🚀}
+
+MobX 7 is mostly a cleanup release. Most applications that already use MobX 6 idiomatically can upgrade with minimal changes.
+
+## Updating React bindings
+
+MobX 7 keeps the React bindings split:
+
+- `mobx-react-lite` supports function components and `forwardRef`.
+- `mobx-react` is a thin wrapper around `mobx-react-lite` that also supports class components and the `@observer` class decorator.
+
+`mobx-react-lite` and `mobx-react` require React 18 or later.
+
+The public React binding surface has been reduced to the APIs that are still recommended:
+
+- `observer`
+- `Observer`
+- `useLocalObservable`
+- `enableStaticRendering`
+- `isUsingStaticRendering`
+
+The following APIs have been removed:
+
+| Removed API | Replacement |
+| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
+| `disposeOnUnmount` | Dispose reactions in `componentWillUnmount`, or return a cleanup function from `useEffect`. |
+| `PropTypes` | Use TypeScript or the regular `prop-types` package. |
+| `useLocalStore` | Use `useLocalObservable`. |
+| `useAsObservableSource` | Store the values you need locally and synchronize them from props with `useEffect`. |
+| `useObserver` | Wrap the component in `observer`, or use the `` component. |
+| `useStaticRendering` | Use `enableStaticRendering`. |
+| `observerBatching`, `isObserverBatched`, `batchingForReactDom`, `batchingOptOut`, `batchingForReactNative` | Remove these imports. React 18+ renderers handle batching automatically, and the React Native side-effect import is no longer needed. |
+| `Provider`, `inject`, `MobXProviderContext` from `mobx-react` | Use `React.createContext` directly. |
+
+## Migrating legacy decorators
+
+MobX 7 supports Stage 3 decorators only.
+
+To keep decorators, switch the class to Stage 3 decorator syntax: remove `makeObservable(this)` from decorated classes, drop its import when unused, and add `accessor` to observable fields.
+
+```diff
+-import { makeObservable, observable, computed, action } from "mobx"
++import { observable, computed, action } from "mobx"
+
+class Todo {
+- @observable title = ""
++ @observable accessor title = ""
+- @observable finished = false
++ @observable accessor finished = false
+-
+- constructor() {
+- makeObservable(this)
+- }
+
+ @computed
+ get label() {
+ return `${this.finished ? "[DONE]" : "[OPEN]"} ${this.title}`
+ }
+
+ @action
+ toggle() {
+ this.finished = !this.finished
+ }
+}
+```
+
+Switch your compiler to modern decorators:
+
+- For TypeScript, use TypeScript 5 or later and disable or remove the `experimentalDecorators` flag.
+- For Babel, use `@babel/plugin-proposal-decorators` with the current Stage 3 configuration. See [Enabling decorators {🚀}](enabling-decorators.md) for the exact compiler setup.
+
+If you don't want to keep decorators, remove them and pass an explicit annotation map to `makeObservable`:
+
+```javascript
+import { makeObservable, observable, computed, action } from "mobx"
+
+class Todo {
+ title = ""
+ finished = false
+
+ constructor() {
+ makeObservable(this, {
+ title: observable,
+ finished: observable,
+ label: computed,
+ toggle: action
+ })
+ }
+
+ get label() {
+ return `${this.finished ? "[DONE]" : "[OPEN]"} ${this.title}`
+ }
+
+ toggle() {
+ this.finished = !this.finished
+ }
+}
+```
+
+## Replacing namespaced APIs
+
+Namespaced annotation and comparer properties have been replaced by named exports for better tree-shaking:
+
+| 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` |
+
+Annotation map:
+
+```diff
+-import { action, comparer, computed, flow, makeObservable, observable } from "mobx"
++import { actionBound, compareStructural, computed, computedStruct, flowBound, makeObservable, observableRef } from "mobx"
+
+ makeObservable(this, {
+- value: observable.ref,
++ value: observableRef,
+- total: computed.struct,
++ total: computedStruct,
+- rounded: computed({ equals: comparer.structural }),
++ rounded: computed({ equals: compareStructural }),
+- save: action.bound,
++ save: actionBound,
+- load: flow.bound
++ load: flowBound
+ })
+```
+
+Decorator:
+
+```diff
+-import { action, comparer, computed, flow, observable } from "mobx"
++import { actionBound, compareStructural, computed, computedStruct, flowBound, observableRef } from "mobx"
+
+ class Store {
+- @observable.ref accessor value = null
++ @observableRef accessor value = null
+
+- @computed.struct
++ @computedStruct
+ get total() {
+ return { value: this.value }
+ }
+
+- @computed({ equals: comparer.structural })
++ @computed({ equals: compareStructural })
+ get rounded() {
+ return { value: Math.round(this.value) }
+ }
+
+- @action.bound
++ @actionBound
+ save() {}
+
+- @flow.bound
++ @flowBound
+ *load() {}
+ }
+```
+
+Old structural boolean options should use `equals` explicitly:
+
+```diff
+-computed(() => value, { compareStructural: true })
++computed(() => value, { equals: compareStructural })
+
+-reaction(() => value, effect, { compareStructural: true })
++reaction(() => value, effect, { equals: compareStructural })
+```
+
+## Proxy support is required
+
+MobX 7 requires native Proxy support and no longer includes the ES5 fallback implementation.
+
+Remove `useProxies` from `configure` calls:
+
+```diff
+ import { configure } from "mobx"
+
+ configure({
+ enforceActions: "observed",
+- useProxies: "ifavailable"
+ })
+```
+
+Also remove `{ proxy: false }` from `observable`, `observable.object` and `observable.array` options:
+
+```diff
+-const todos = observable.object({}, {}, { proxy: false })
++const todos = observable.object({})
+```
+
+## Removed `trace`
+
+The `trace` API has been removed. For debugging reactivity, use [`getDependencyTree`](api.md#getdependencytree), [`getObserverTree`](api.md#getobservertree), [`spy`](analyzing-reactivity.md#spy), the MobX developer tools, or packages such as `mobx-log`.
+
+```javascript
+import { autorun, getDependencyTree } from "mobx"
+
+const disposer = autorun(() => {
+ console.log(message.title)
+})
+
+console.log(getDependencyTree(disposer))
+```
+
+## Replacing `Provider` and `inject`
+
+`Provider` and `inject` were removed. Use React context directly. Keep the context value stable and mutate the observable store instead of replacing the provider value.
+
+Before:
+
+```javascript
+import { Provider, inject, observer } from "mobx-react"
+
+// prettier-ignore
+const UserName = inject("userStore")(
+ observer(({ userStore }) => {userStore.name})
+)
+
+const App = ({ userStore }) => (
+
+
+
+)
+```
+
+After, using a function component:
+
+```javascript
+import React, { createContext, useContext } from "react"
+import { observer } from "mobx-react"
+
+const RootStoreContext = createContext(null)
+
+export const RootStoreProvider = ({ rootStore, children }) => (
+ {children}
+)
+
+export const useRootStore = () => {
+ const store = useContext(RootStoreContext)
+ if (!store) {
+ throw new Error("RootStoreProvider is missing")
+ }
+ return store
+}
+
+const UserName = observer(() => {
+ const { userStore } = useRootStore()
+ return {userStore.name}
+})
+
+const App = () => (
+
+
+
+)
+```
+
+After, using a class component:
+
+```javascript
+import React from "react"
+import { observer } from "mobx-react"
+
+const RootStoreContext = React.createContext(null)
+
+class UserName extends React.Component {
+ static contextType = RootStoreContext
+
+ render() {
+ const { userStore } = this.context
+ return {userStore.name}
+ }
+}
+
+const ObservedUserName = observer(UserName)
+```
diff --git a/docs/observable-state.md b/docs/observable-state.md
index c7311ad984..b2d01c99a0 100644
--- a/docs/observable-state.md
+++ b/docs/observable-state.md
@@ -20,7 +20,7 @@ The most important annotations are:
Usage:
-- `makeObservable(target, annotations?, options?)`
+- `makeObservable(target, annotations, options?)`
This function can be used to make _existing_ object properties observable. Any JavaScript object (including class instances) can be passed into `target`.
Typically `makeObservable` is used in the constructor of a class, and its first argument is `this`.
@@ -72,7 +72,7 @@ class Doubler {
When using modern decorators, there is no need to call `makeObservable`, below is what a decorator based class looks like.
-Note that the `@observable` annotation should always be used in combination with the `accessor` keyword.
+Note that the `@observable` decorator should always be used in combination with the `accessor` keyword.
```javascript
import { observable, computed, action, flow } from "mobx"
@@ -147,39 +147,6 @@ tags.push("prio: for fun")
In contrast to the first example with `makeObservable`, `observable` supports adding (and removing) _fields_ to an object.
This makes `observable` great for collections like dynamically keyed objects, arrays, Maps and Sets.
-
-
-To use legacy decorators, `makeObservable(this)` should be called in the constructor to make sure decorators work.
-
-```javascript
-import { observable, computed, action, flow } from "mobx"
-
-class Doubler {
- @observable value
-
- constructor(value) {
- makeObservable(this)
- this.value = value
- }
-
- @computed
- get double() {
- return this.value * 2
- }
-
- @action
- increment() {
- this.value++
- }
-
- @flow
- *fetch() {
- const response = yield fetch("/api/value")
- this.value = response.json()
- }
-}
-```
-
## `makeAutoObservable`
@@ -216,7 +183,7 @@ The `source` object will be cloned and all members will be made observable, simi
Likewise, an `overrides` map can be provided to specify the annotations of specific members.
Check out the above code block for an example.
-The object returned by `observable` will be a Proxy, which means that properties that are added later to the object will be picked up and made observable as well (except when [proxy usage](configuration.md#proxy-support) is disabled).
+The object returned by `observable` will be a Proxy, which means that properties that are added later to the object will be picked up and made observable as well.
The `observable` method can also be called with collections types like [arrays](api.md#observablearray), [Maps](api.md#observablemap) and [Sets](api.md#observableset). Those will be cloned as well and converted into their observable counterparts.
@@ -274,36 +241,35 @@ Making class members observable is considered the responsibility of the class co
-{🚀} **Tip:** observable (proxied) versus makeObservable (unproxied)
+{🚀} **Tip:** observable clones versus makeObservable in-place updates
The primary difference between `make(Auto)Observable` and `observable` is that the first one modifies the object you are passing in as first argument, while `observable` creates a _clone_ that is made observable.
-The second difference is that `observable` creates a [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) object, to be able to trap future property additions in case you use the object as a dynamic lookup map.
-If the object you want to make observable has a regular structure where all members are known up-front, we recommend to use `makeObservable` as non proxied objects are a little faster, and they are easier to inspect in the debugger and `console.log`.
+`observable` creates a [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) object, to be able to trap future property additions in case you use the object as a dynamic lookup map.
+If the object you want to make observable has a regular structure where all members are known up-front, `makeObservable` is often the clearer API because it keeps the original object identity.
Because of that, `make(Auto)Observable` is the recommended API to use in factory functions.
-Note that it is possible to pass `{ proxy: false }` as an option to `observable` to get a non proxied clone.
## Available annotations
-| Annotation | Description |
-| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Annotation | Description |
+| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `observable` `observable.deep` | Defines a trackable field that stores state. If possible, any value assigned to `observable` is automatically converted to (deep) `observable`, [`autoAction`](#autoAction) or `flow` based on its type. Only `plain object`, `array`, `Map`, `Set`, `function`, `generator function` are convertible. Class instances and others are untouched. |
-| `observable.ref` | Like `observable`, but only reassignments will be tracked. The assigned values are completely ignored and will NOT be automatically converted to `observable`/[`autoAction`](#autoAction)/`flow`. For example, use this if you intend to store immutable data in an observable field. |
-| `observable.shallow` | Like `observable.ref` but for collections. Any collection assigned will be made observable, but the contents of the collection itself won't become observable. |
-| `observable.struct` | Like `observable`, except that any assigned value that is structurally equal to the current value will be ignored. |
-| `action` | Mark a method as an action that will modify the state. Check out [actions](actions.md) for more details. Non-writable. |
-| `action.bound` | Like action, but will also bind the action to the instance so that `this` will always be set. Non-writable. |
-| `computed` | Can be used on a [getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) to declare it as a derived value that can be cached. Check out [computeds](computeds.md) for more details. |
-| `computed.struct` | Like `computed`, except that if after recomputing the result is structurally equal to the previous result, no observers will be notified. |
-| `true` | Infer the best annotation. Check out [makeAutoObservable](#makeautoobservable) for more details. |
-| `false` | Explicitly do not annotate this property. |
-| `flow` | Creates a `flow` to manage asynchronous processes. Check out [flow](actions.md#using-flow-instead-of-async--await-) for more details. Note that the inferred return type in TypeScript might be off. Non-writable. |
-| `flow.bound` | Like flow, but will also bind the flow to the instance so that `this` will always be set. Non-writable. |
-| `override` | [Applicable to inherited `action`, `flow`, `computed`, `action.bound` overridden by subclass](subclassing.md). |
-| `autoAction` | Should not be used explicitly, but is used under the hood by `makeAutoObservable` to mark methods that can act as action or derivation, based on their calling context. It will be determined at runtime if the function is a derivation or action. |
+| `observable.ref` | Like `observable`, but only reassignments will be tracked. The assigned values are completely ignored and will NOT be automatically converted to `observable`/[`autoAction`](#autoAction)/`flow`. For example, use this if you intend to store immutable data in an observable field. |
+| `observable.shallow` | Like `observable.ref` but for collections. Any collection assigned will be made observable, but the contents of the collection itself won't become observable. |
+| `observable.struct` | Like `observable`, except that any assigned value that is structurally equal to the current value will be ignored. |
+| `action` | Mark a method as an action that will modify the state. Check out [actions](actions.md) for more details. Non-writable. |
+| `action.bound` | Like action, but will also bind the action to the instance so that `this` will always be set. Non-writable. |
+| `computed` | Can be used on a [getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) to declare it as a derived value that can be cached. Check out [computeds](computeds.md) for more details. |
+| `computed.struct` | Like `computed`, except that if after recomputing the result is structurally equal to the previous result, no observers will be notified. |
+| `true` | Infer the best annotation. Check out [makeAutoObservable](#makeautoobservable) for more details. |
+| `false` | Explicitly do not annotate this property. |
+| `flow` | Creates a `flow` to manage asynchronous processes. Check out [flow](actions.md#using-flow-instead-of-async--await-) for more details. Note that the inferred return type in TypeScript might be off. Non-writable. |
+| `flow.bound` | Like flow, but will also bind the flow to the instance so that `this` will always be set. Non-writable. |
+| `override` | [Applicable to inherited `action`, `flow`, `computed`, `action.bound` overridden by subclass](subclassing.md). |
+| `autoAction` | Should not be used explicitly, but is used under the hood by `makeAutoObservable` to mark methods that can act as action or derivation, based on their calling context. It will be determined at runtime if the function is a derivation or action. |
## Limitations
@@ -331,7 +297,6 @@ The above APIs take an optional `options` argument which is an object that suppo
- **`autoBind: true`** uses `action.bound`/`flow.bound` by default, rather than `action`/`flow`. Does not affect explicitly annotated members.
- **`deep: false`** uses `observable.ref` by default, rather than `observable`. Does not affect explicitly annotated members.
- **`name: `** gives the object a debug name that is printed in error messages and reflection APIs.
-- **`proxy: false`** forces `observable(thing)` to use non-[**proxy**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) implementation. This is a good option if the shape of the object will not change over time, as non-proxied objects are easier to debug and faster. This option is **not** available for `make(Auto)Observable`, see [avoiding proxies](#avoid-proxies).
**Note:** options are *sticky* and can be provided only once
`options` argument can be provided only for `target` that is NOT observable yet.
diff --git a/docs/react-integration.md b/docs/react-integration.md
index b86c41a085..8ab2197a0d 100644
--- a/docs/react-integration.md
+++ b/docs/react-integration.md
@@ -18,7 +18,7 @@ const MyComponent = observer(props => ReactElement)
While MobX works independently from React, they are most commonly used together. In [The gist of MobX](the-gist-of-mobx.md) you have already seen the most important part of this integration: the `observer` [HoC](https://reactjs.org/docs/higher-order-components.html) that you can wrap around a React component.
-`observer` is provided by a separate React bindings package you choose [during installation](installation.md#installation). In this example, we're going to use the more lightweight [`mobx-react-lite` package](https://github.com/mobxjs/mobx/tree/main/packages/mobx-react-lite).
+`observer` is provided by the [`mobx-react-lite` package](https://github.com/mobxjs/mobx/tree/main/packages/mobx-react-lite) for function components. Use [`mobx-react`](https://github.com/mobxjs/mobx/tree/main/packages/mobx-react) if you also need class component support.
```javascript
import React from "react"
@@ -198,7 +198,7 @@ ReactDOM.render(, document.body)
The combination `const [store] = useState(() => observable({ /* something */}))` is
-quite common. To make this pattern simpler the [`useLocalObservable`](https://github.com/mobxjs/mobx-react#uselocalobservable-hook) hook is exposed from `mobx-react-lite` package, making it possible to simplify the earlier example to:
+quite common. To make this pattern simpler the [`useLocalObservable`](https://github.com/mobxjs/mobx/tree/main/packages/mobx-react-lite#uselocalobservabletinitializer--t-annotations-annotationsmapt-t) hook is exposed from the `mobx-react-lite` package, making it possible to simplify the earlier example to:
```javascript
import { observer, useLocalObservable } from "mobx-react-lite"
@@ -291,7 +291,7 @@ const TodoView = observer(({ todo }: { todo: Todo }) =>
Imagine the same example, where `GridRow` takes an `onRender` callback instead.
Since `onRender` is part of the rendering cycle of `GridRow`, rather than `TodoView`'s render (even though that is where it syntactically appears), we have to make sure that the callback component uses an `observer` component.
-Or, we can create an in-line anonymous observer using [``](https://github.com/mobxjs/mobx-react#observer):
+Or, we can create an in-line anonymous observer using [``](https://github.com/mobxjs/mobx/tree/main/packages/mobx-react-lite#observerrenderfn):
```javascript
const TodoView = observer(({ todo }: { todo: Todo }) => {
@@ -310,20 +310,6 @@ const TodoView = observer(({ todo }: { todo: Todo }) => {
If `observer` is used in server side rendering context; make sure to call `enableStaticRendering(true)`, so that `observer` won't subscribe to any observables used, and no GC problems are introduced.
-**Note:** mobx-react vs. mobx-react-lite
-In this documentation we used `mobx-react-lite` as default.
-[mobx-react](https://github.com/mobxjs/mobx-react/) is its big brother, which uses `mobx-react-lite` under the hood.
-It offers a few more features which are typically not needed anymore in greenfield projects. The additional things offered by mobx-react:
-
-1. Support for React class components.
-1. `Provider` and `inject`. MobX's own React.createContext predecessor which is not needed anymore.
-1. Observable specific `propTypes`.
-
-Note that `mobx-react` fully repackages and re-exports `mobx-react-lite`, including functional component support.
-If you use `mobx-react`, there is no need to add `mobx-react-lite` as a dependency or import from it anywhere.
-
-
-
**Note:** `observer` or `React.memo`?
`observer` automatically applies `memo`, so `observer` components never need to be wrapped in `memo`.
`memo` can be applied safely to observer components because mutations (deeply) inside the props will be picked up by `observer` anyway if relevant.
@@ -331,12 +317,12 @@ If you use `mobx-react`, there is no need to add `mobx-react-lite` as a dependen
**Tip:** `observer` for class based React components
-As stated above, class based components are only supported through `mobx-react`, and not `mobx-react-lite`.
Briefly, you can wrap class-based components in `observer` just like
you can wrap function components:
```javascript
import React from "react"
+import { observer } from "mobx-react"
const TimerView = observer(
class TimerView extends React.Component {
@@ -368,7 +354,7 @@ then no display name will be visible in the DevTools.
The following approaches can be used to fix this:
-- use `function` with a name instead of an arrow function. `mobx-react` infers component name from the function name:
+- use `function` with a name instead of an arrow function. `observer` infers component name from the function name:
```javascript
export const MyComponent = observer(function MyComponent(props) {
@@ -390,15 +376,13 @@ The following approaches can be used to fix this:
export default observer(MyComponent)
```
-- [**Broken**] Set `displayName` explicitly:
+- Set `displayName` explicitly:
```javascript
export const MyComponent = observer(props =>
-}
-
-// After:
-function Measurement({ unit }) {
- const state = useLocalObservable(() => ({
- unit, // the initial unit
- length: 0,
- get lengthWithUnit() {
- // lengthWithUnit can only depend on observables, hence the above conversion with `useAsObservableSource`
- return this.unit === "inch" ? `${this.length / 2.54} inch` : `${this.length} cm`
- }
- }))
-
- useEffect(() => {
- // sync the unit from 'props' into the observable 'state'
- state.unit = unit
- }, [unit])
-
- return
{state.lengthWithUnit}
-}
-```
-
-Note that, at your own risk, it is also possible to not use `useEffect`, but do `state.unit = unit` instead in the rendering.
-This is closer to the old behavior, but React will warn correctly about this if this would affect the rendering of other components.
-
-## Observer batching (deprecated)
-
-_Note: configuring observer batching is only needed when using `mobx-react-lite` 2.0.* or 2.1.*. From 2.2 onward it will be configured automatically based on the availability of react-dom / react-native packages_
-
-[Check out the elaborate explanation](https://github.com/mobxjs/mobx-react/pull/787#issuecomment-573599793).
-
-In short without observer batching the React doesn't guarantee the order component rendering in some cases. We highly recommend that you configure batching to avoid these random surprises.
-
-Import one of these before any React rendering is happening, typically `index.js/ts`. For Jest tests you can utilize [setupFilesAfterEnv](https://jestjs.io/docs/en/configuration#setupfilesafterenv-array).
-
-**React DOM:**
-
-> import 'mobx-react-lite/batchingForReactDom'
-
-**React Native:**
-
-> import 'mobx-react-lite/batchingForReactNative'
-
-### Opt-out
-
-To opt-out from batching in some specific cases, simply import the following to silence the warning.
-
-> import 'mobx-react-lite/batchingOptOut'
-
-### Custom batched updates
-
-Above imports are for a convenience to utilize standard versions of batching. If you for some reason have customized version of batched updates, you can do the following instead.
-
-```js
-import { observerBatching } from "mobx-react-lite"
-observerBatching(customBatchedUpdates)
-```
+`useObserver`, `useLocalStore`, `useAsObservableSource`, `useStaticRendering`, batching imports, `observerBatching`, and `isObserverBatched` have been removed. Use `observer`, ``, `useLocalObservable`, and `enableStaticRendering`; React 18 renderers handle batching.
## Testing
diff --git a/packages/mobx-react-lite/__tests__/ObserverComponent.test.tsx b/packages/mobx-react-lite/__tests__/ObserverComponent.test.tsx
index b533ed756e..8f5a0446dc 100644
--- a/packages/mobx-react-lite/__tests__/ObserverComponent.test.tsx
+++ b/packages/mobx-react-lite/__tests__/ObserverComponent.test.tsx
@@ -1,12 +1,10 @@
import mockConsole from "jest-mock-console"
import * as mobx from "mobx"
import * as React from "react"
-import { act, cleanup, render } from "@testing-library/react"
+import { act, render } from "@testing-library/react"
import { Observer } from "../src"
-afterEach(cleanup)
-
describe("regions should rerender component", () => {
const execute = () => {
const data = mobx.observable.box("hi")
@@ -43,13 +41,13 @@ it("renders null if no children/render prop is supplied a function", () => {
restoreConsole()
})
-it.skip("prop types checks for children/render usage", () => {
+it("prop types checks for children/render usage", () => {
const Comp = () => (
+ // @ts-expect-error render and children are mutually exclusive
children}>{() => children}
)
const restoreConsole = mockConsole("error")
render()
- // tslint:disable-next-line:no-console
expect(console.error).toHaveBeenCalledWith(
expect.stringContaining("Do not use children and render in the same time")
)
diff --git a/packages/mobx-react-lite/__tests__/__snapshots__/observer.test.tsx.snap b/packages/mobx-react-lite/__tests__/__snapshots__/observer.test.tsx.snap
index aa7385fc12..802eb2588b 100644
--- a/packages/mobx-react-lite/__tests__/__snapshots__/observer.test.tsx.snap
+++ b/packages/mobx-react-lite/__tests__/__snapshots__/observer.test.tsx.snap
@@ -64,52 +64,4 @@ exports[`issue 12 run transaction 2`] = `
`;
-exports[`issue 309 isObserverBatched is still defined and yields true by default 1`] = `
-[MockFunction] {
- "calls": [
- [
- "[MobX] Deprecated",
- ],
- ],
- "results": [
- {
- "type": "return",
- "value": undefined,
- },
- ],
-}
-`;
-
-exports[`issue 309 isObserverBatched is still defined and yields true by default 2`] = `
-[MockFunction] {
- "calls": [
- [
- "[MobX] Deprecated",
- ],
- ],
- "results": [
- {
- "type": "return",
- "value": undefined,
- },
- ],
-}
-`;
-
-exports[`observer(cmp, { forwardRef: true }) + useImperativeHandle 1`] = `
-[MockFunction] {
- "calls": [
- [
- "[mobx-react-lite] \`observer(fn, { forwardRef: true })\` is deprecated, use \`observer(React.forwardRef(fn))\`",
- ],
- ],
- "results": [
- {
- "type": "return",
- "value": undefined,
- },
- ],
-}
-`;
-
exports[`useImperativeHandle and forwardRef should work with useObserver 1`] = `[MockFunction]`;
diff --git a/packages/mobx-react-lite/__tests__/__snapshots__/printDebugValue.test.ts.snap b/packages/mobx-react-lite/__tests__/__snapshots__/printDebugValue.test.ts.snap
deleted file mode 100644
index 5cd288b65e..0000000000
--- a/packages/mobx-react-lite/__tests__/__snapshots__/printDebugValue.test.ts.snap
+++ /dev/null
@@ -1,26 +0,0 @@
-// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
-
-exports[`printDebugValue 1`] = `
-{
- "dependencies": [
- {
- "name": "ObservableObject@1.euro",
- },
- {
- "dependencies": [
- {
- "name": "ObservableObject@1.euro",
- },
- ],
- "name": "ObservableObject@1.pound",
- },
- ],
- "name": "Autorun@2",
-}
-`;
-
-exports[`printDebugValue 2`] = `
-{
- "name": "Autorun@2",
-}
-`;
diff --git a/packages/mobx-react-lite/__tests__/__snapshots__/useAsObservableSource.deprecated.test.tsx.snap b/packages/mobx-react-lite/__tests__/__snapshots__/useAsObservableSource.deprecated.test.tsx.snap
deleted file mode 100644
index 88ba3f79b2..0000000000
--- a/packages/mobx-react-lite/__tests__/__snapshots__/useAsObservableSource.deprecated.test.tsx.snap
+++ /dev/null
@@ -1,24 +0,0 @@
-// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
-
-exports[`base useAsObservableSource should work with 1`] = `
-[MockFunction] {
- "calls": [
- [
- "[mobx-react-lite] 'useAsObservableSource' is deprecated, please store the values directly in an observable, for example by using 'useLocalObservable', and sync future updates using 'useEffect' when needed. See the README for examples.",
- ],
- [
- "[mobx-react-lite] 'useLocalStore' is deprecated, use 'useLocalObservable' instead.",
- ],
- ],
- "results": [
- {
- "type": "return",
- "value": undefined,
- },
- {
- "type": "return",
- "value": undefined,
- },
- ],
-}
-`;
diff --git a/packages/mobx-react-lite/__tests__/__snapshots__/useLocalStore.deprecated.test.tsx.snap b/packages/mobx-react-lite/__tests__/__snapshots__/useLocalStore.deprecated.test.tsx.snap
deleted file mode 100644
index 5e9338971b..0000000000
--- a/packages/mobx-react-lite/__tests__/__snapshots__/useLocalStore.deprecated.test.tsx.snap
+++ /dev/null
@@ -1,33 +0,0 @@
-// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
-
-exports[`base useLocalStore should work 1`] = `
-[MockFunction] {
- "calls": [
- [
- "[mobx-react-lite] 'useLocalStore' is deprecated, use 'useLocalObservable' instead.",
- ],
- ],
- "results": [
- {
- "type": "return",
- "value": undefined,
- },
- ],
-}
-`;
-
-exports[`is used to keep observable within component body with props and useObserver 1`] = `
-[MockFunction] {
- "calls": [
- [
- "[mobx-react-lite] 'useAsObservableSource' is deprecated, please store the values directly in an observable, for example by using 'useLocalObservable', and sync future updates using 'useEffect' when needed. See the README for examples.",
- ],
- ],
- "results": [
- {
- "type": "return",
- "value": undefined,
- },
- ],
-}
-`;
diff --git a/packages/mobx-react-lite/__tests__/api.test.ts b/packages/mobx-react-lite/__tests__/api.test.tsx
similarity index 71%
rename from packages/mobx-react-lite/__tests__/api.test.ts
rename to packages/mobx-react-lite/__tests__/api.test.tsx
index a2f7cf57e6..d14bd4138e 100644
--- a/packages/mobx-react-lite/__tests__/api.test.ts
+++ b/packages/mobx-react-lite/__tests__/api.test.tsx
@@ -12,13 +12,7 @@ test("correct api should be exposed", function () {
"observer",
"Observer",
"useLocalObservable",
- "useLocalStore",
- "useAsObservableSource",
"clearTimers",
- "useObserver",
- "isObserverBatched",
- "observerBatching",
- "useStaticRendering",
"_observerFinalizationRegistry"
].sort()
)
diff --git a/packages/mobx-react-lite/__tests__/assertEnvironment.test.ts b/packages/mobx-react-lite/__tests__/assertEnvironment.test.ts
deleted file mode 100644
index 33c414e844..0000000000
--- a/packages/mobx-react-lite/__tests__/assertEnvironment.test.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-afterEach(() => {
- jest.resetModules()
- jest.resetAllMocks()
-})
-
-it("throws if react is not installed", () => {
- jest.mock("react", () => ({}))
- expect(() => require("../src/utils/assertEnvironment.ts")).toThrowErrorMatchingInlineSnapshot(
- `"mobx-react-lite requires React with Hooks support"`
- )
-})
-
-it("throws if mobx is not installed", () => {
- jest.mock("react", () => ({ useState: true }))
- jest.mock("mobx", () => ({}))
- expect(() => require("../src/utils/assertEnvironment.ts")).toThrowErrorMatchingInlineSnapshot(
- `"mobx-react-lite@3 requires mobx at least version 6 to be available"`
- )
-})
-
-export default "Cannot use import statement outside a module"
diff --git a/packages/mobx-react-lite/__tests__/assertEnvironment.test.tsx b/packages/mobx-react-lite/__tests__/assertEnvironment.test.tsx
new file mode 100644
index 0000000000..64ebec3afa
--- /dev/null
+++ b/packages/mobx-react-lite/__tests__/assertEnvironment.test.tsx
@@ -0,0 +1,31 @@
+afterEach(() => {
+ jest.resetModules()
+ jest.resetAllMocks()
+})
+
+it("throws if react is not installed", () => {
+ jest.mock("react", () => ({}))
+ expect(() => require("../src/utils/assertEnvironment.ts")).toThrowErrorMatchingInlineSnapshot(
+ `"mobx-react-lite requires React 18 or later"`
+ )
+})
+
+it("throws if mobx is not installed", () => {
+ jest.mock("react", () => ({ useState: true, useSyncExternalStore: true }))
+ jest.mock("mobx", () => ({}))
+ expect(() => require("../src/utils/assertEnvironment.ts")).toThrowErrorMatchingInlineSnapshot(
+ `"mobx-react-lite requires mobx at least version 7 to be available"`
+ )
+})
+
+it("throws if mobx is older than version 7", () => {
+ jest.mock("react", () => ({ useState: true, useSyncExternalStore: true }))
+ jest.mock("mobx", () => ({
+ _getGlobalState: () => ({ version: 6 })
+ }))
+ expect(() => require("../src/utils/assertEnvironment.ts")).toThrowErrorMatchingInlineSnapshot(
+ `"mobx-react-lite requires mobx at least version 7 to be available"`
+ )
+})
+
+export default "Cannot use import statement outside a module"
diff --git a/packages/mobx-react-lite/__tests__/enforceActions.test.tsx b/packages/mobx-react-lite/__tests__/enforceActions.test.tsx
index c26c51b1f0..1758129dc3 100644
--- a/packages/mobx-react-lite/__tests__/enforceActions.test.tsx
+++ b/packages/mobx-react-lite/__tests__/enforceActions.test.tsx
@@ -1,18 +1,10 @@
import * as mobx from "mobx"
-import { _resetGlobalState } from "mobx"
import * as React from "react"
import { useEffect } from "react"
-import { observer, useLocalObservable } from "mobx-react"
+import { observer, useLocalObservable } from "../src"
import { render } from "@testing-library/react"
let consoleWarnMock: jest.SpyInstance | undefined
-afterEach(() => {
- consoleWarnMock?.mockRestore()
-})
-
-afterEach(() => {
- _resetGlobalState()
-})
describe("enforcing actions", () => {
it("'never' should work", () => {
diff --git a/packages/mobx-react-lite/__tests__/observer.test.tsx b/packages/mobx-react-lite/__tests__/observer.test.tsx
index 1083c94a04..f522c2e73a 100644
--- a/packages/mobx-react-lite/__tests__/observer.test.tsx
+++ b/packages/mobx-react-lite/__tests__/observer.test.tsx
@@ -1,18 +1,14 @@
-import { act, cleanup, fireEvent, render } from "@testing-library/react"
+import { act, fireEvent, render } from "@testing-library/react"
import mockConsole from "jest-mock-console"
import * as mobx from "mobx"
import React from "react"
-import { observer, useObserver, isObserverBatched, enableStaticRendering } from "../src"
+import { observer, enableStaticRendering } from "../src"
+import { useObserver } from "../src/useObserver"
const getDNode = (obj: any, prop?: string) => mobx.getObserverTree(obj, prop)
-afterEach(cleanup)
-
let consoleWarnMock: jest.SpyInstance | undefined
-afterEach(() => {
- consoleWarnMock?.mockRestore()
-})
function runTestSuite(mode: "observer" | "useObserver") {
function obsComponent
(
@@ -274,14 +270,6 @@ function runTestSuite(mode: "observer" | "useObserver") {
})
})
- describe("issue 309", () => {
- test("isObserverBatched is still defined and yields true by default", () => {
- consoleWarnMock = jest.spyOn(console, "warn").mockImplementation(() => {})
- expect(isObserverBatched()).toBe(true)
- expect(consoleWarnMock).toMatchSnapshot()
- })
- })
-
test("changing state in render should fail", () => {
// This test is most likely obsolete ... exception is not thrown
const data = mobx.observable.box(2)
@@ -303,7 +291,6 @@ function runTestSuite(mode: "observer" | "useObserver") {
data.set(3)
})
expect(container).toMatchSnapshot()
- mobx._resetGlobalState()
})
describe("should render component even if setState called with exactly the same props", () => {
@@ -489,43 +476,6 @@ function runTestSuite(mode: "observer" | "useObserver") {
runTestSuite("observer")
runTestSuite("useObserver")
-test("observer(cmp, { forwardRef: true }) + useImperativeHandle", () => {
- consoleWarnMock = jest.spyOn(console, "warn").mockImplementation(() => {})
-
- interface IMethods {
- focus(): void
- }
-
- interface IProps {
- value: string
- ref: React.Ref
- }
-
- const FancyInput = observer(
- (props: IProps, ref: React.Ref) => {
- const inputRef = React.useRef(null)
- React.useImperativeHandle(
- ref,
- () => ({
- focus: () => {
- inputRef.current!.focus()
- }
- }),
- []
- )
- return
- },
- { forwardRef: true }
- )
-
- const cr = React.createRef()
- render()
- expect(cr).toBeTruthy()
- expect(cr.current).toBeTruthy()
- expect(typeof cr.current!.focus).toBe("function")
- expect(consoleWarnMock).toMatchSnapshot()
-})
-
test("observer(forwardRef(cmp)) + useImperativeHandle", () => {
interface IMethods {
focus(): void
@@ -734,23 +684,7 @@ it("should have overload for props with children", () => {
// this test has no `expect` calls as it verifies whether such component compiles or not
})
-it("should have overload for empty options", () => {
- // empty options are not really making sense now, but we shouldn't rely on `forwardRef`
- // being specified in case other options are added in the future
-
- interface IProps {
- value: string
- }
- const TestComponent = observer(({ value }) => {
- return null
- }, {})
-
- render()
-
- // this test has no `expect` calls as it verifies whether such component compiles or not
-})
-
-it("should have overload for props with children when forwardRef", () => {
+it("should have overload for props with children when using forwardRef", () => {
interface IMethods {
focus(): void
}
@@ -758,11 +692,10 @@ it("should have overload for props with children when forwardRef", () => {
interface IProps {
value: string
}
- const TestComponent = observer(
- ({ value }, ref) => {
+ const TestComponent = observer(
+ React.forwardRef(({ value }, ref) => {
return null
- },
- { forwardRef: true }
+ })
)
render()
@@ -799,38 +732,26 @@ it("should preserve generic parameters", () => {
// this test has no `expect` calls as it verifies whether such component compiles or not
})
-it("should preserve generic parameters when forwardRef", () => {
+it("should preserve concrete props when using forwardRef", () => {
interface IMethods {
focus(): void
}
- interface IColor {
- name: string
- css: string
- }
-
- interface ITestComponentProps {
- value: T
- callback: (value: T) => void
+ interface ITestComponentProps {
+ value: string
+ callback: (value: string) => void
}
const TestComponent = observer(
- (props: ITestComponentProps, ref: React.Ref) => {
+ React.forwardRef((props, ref) => {
return null
- },
- { forwardRef: true }
+ })
)
function callbackString(value: string) {
return
}
- function callbackColor(value: IColor) {
- return
- }
render()
- render(
-
- )
// this test has no `expect` calls as it verifies whether such component compiles or not
})
@@ -1056,21 +977,6 @@ it.skip("Legacy context support", () => {
render()
})
-it("Throw when trying to set contextType on observer", () => {
- const NamedObserver = observer(function TestCmp() {
- return null
- })
- const AnonymousObserver = observer(() => null)
- expect(() => {
- ;(NamedObserver as any).contextTypes = {}
- }).toThrow(/\[mobx-react-lite\] `TestCmp.contextTypes` must be set before applying `observer`./)
- expect(() => {
- ;(AnonymousObserver as any).contextTypes = {}
- }).toThrow(
- /\[mobx-react-lite\] `Component.contextTypes` must be set before applying `observer`./
- )
-})
-
test("Anonymous component displayName #3192", () => {
// React prints errors even if we catch em
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {})
@@ -1152,6 +1058,4 @@ test("`isolateGlobalState` shouldn't break reactivity #3734", async () => {
)
expect(container).toHaveTextContent("1")
unmount()
-
- mobx._resetGlobalState()
})
diff --git a/packages/mobx-react-lite/__tests__/printDebugValue.test.ts b/packages/mobx-react-lite/__tests__/printDebugValue.test.ts
deleted file mode 100644
index b383d7327b..0000000000
--- a/packages/mobx-react-lite/__tests__/printDebugValue.test.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { $mobx, autorun, observable } from "mobx"
-import { printDebugValue } from "../src/utils/printDebugValue"
-
-test("printDebugValue", () => {
- const money = observable({
- euro: 10,
- get pound() {
- return this.euro / 1.15
- }
- })
-
- const disposer = autorun(() => {
- const { euro, pound } = money
- if (euro === pound) {
- // tslint:disable-next-line: no-console
- console.log("Weird..")
- }
- })
-
- const value = (disposer as any)[$mobx]
-
- expect(printDebugValue(value)).toMatchSnapshot()
-
- disposer()
-
- expect(printDebugValue(value)).toMatchSnapshot()
-})
diff --git a/packages/mobx-react-lite/__tests__/strictAndConcurrentMode.test.tsx b/packages/mobx-react-lite/__tests__/strictAndConcurrentMode.test.tsx
index 519fd1193f..ff51212235 100644
--- a/packages/mobx-react-lite/__tests__/strictAndConcurrentMode.test.tsx
+++ b/packages/mobx-react-lite/__tests__/strictAndConcurrentMode.test.tsx
@@ -1,12 +1,10 @@
-import { act, cleanup, render } from "@testing-library/react"
+import { act, render } from "@testing-library/react"
import mockConsole from "jest-mock-console"
import * as mobx from "mobx"
import * as React from "react"
import { useObserver } from "../src/useObserver"
-afterEach(cleanup)
-
test("uncommitted observing components should not attempt state changes", () => {
const store = mobx.observable({ count: 0 })
@@ -34,7 +32,6 @@ test("uncommitted observing components should not attempt state changes", () =>
})
// Check to see if any console errors were reported.
- // tslint:disable-next-line: no-console
expect(console.error).not.toHaveBeenCalled()
} finally {
restoreConsole()
diff --git a/packages/mobx-react-lite/__tests__/strictAndConcurrentModeUsingFinalizationRegistry.test.tsx b/packages/mobx-react-lite/__tests__/strictAndConcurrentModeUsingFinalizationRegistry.test.tsx
index 7bfac4c0ce..4e4698ed30 100644
--- a/packages/mobx-react-lite/__tests__/strictAndConcurrentModeUsingFinalizationRegistry.test.tsx
+++ b/packages/mobx-react-lite/__tests__/strictAndConcurrentModeUsingFinalizationRegistry.test.tsx
@@ -1,4 +1,4 @@
-import { cleanup, render, waitFor } from "@testing-library/react"
+import { render, waitFor } from "@testing-library/react"
import * as mobx from "mobx"
import * as React from "react"
import { useObserver } from "../src/useObserver"
@@ -11,8 +11,6 @@ if (typeof globalThis.FinalizationRegistry !== "function") {
expect(observerFinalizationRegistry).toBeInstanceOf(globalThis.FinalizationRegistry)
-afterEach(cleanup)
-
function nextFrame() {
return new Promise(accept => setTimeout(accept, 1))
}
diff --git a/packages/mobx-react-lite/__tests__/strictAndConcurrentModeUsingTimers.test.tsx b/packages/mobx-react-lite/__tests__/strictAndConcurrentModeUsingTimers.test.tsx
index d7250ccb4b..7fc37b26db 100644
--- a/packages/mobx-react-lite/__tests__/strictAndConcurrentModeUsingTimers.test.tsx
+++ b/packages/mobx-react-lite/__tests__/strictAndConcurrentModeUsingTimers.test.tsx
@@ -1,5 +1,5 @@
import "./utils/killFinalizationRegistry"
-import { act, cleanup, render } from "@testing-library/react"
+import { act, render } from "@testing-library/react"
import * as mobx from "mobx"
import * as React from "react"
import { useObserver } from "../src/useObserver"
@@ -14,8 +14,6 @@ expect(observerFinalizationRegistry).toBeInstanceOf(TimerBasedFinalizationRegist
const registry = observerFinalizationRegistry as TimerBasedFinalizationRegistry
-afterEach(cleanup)
-
test("uncommitted components should not leak observations", async () => {
registry.finalizeAllImmediately()
diff --git a/packages/mobx-react-lite/__tests__/useAsObservableSource.deprecated.test.tsx b/packages/mobx-react-lite/__tests__/useAsObservableSource.deprecated.test.tsx
deleted file mode 100644
index 4f6f62a75d..0000000000
--- a/packages/mobx-react-lite/__tests__/useAsObservableSource.deprecated.test.tsx
+++ /dev/null
@@ -1,348 +0,0 @@
-import { act, cleanup, render, renderHook } from "@testing-library/react"
-import { autorun, configure, observable } from "mobx"
-import * as React from "react"
-import { useEffect, useState } from "react"
-
-import { Observer, observer, useAsObservableSource, useLocalStore } from "../src"
-import { resetMobx } from "./utils"
-
-afterEach(cleanup)
-afterEach(resetMobx)
-
-let consoleWarnMock: jest.SpyInstance | undefined
-afterEach(() => {
- consoleWarnMock?.mockRestore()
-})
-
-describe("base useAsObservableSource should work", () => {
- it("with ", () => {
- consoleWarnMock = jest.spyOn(console, "warn").mockImplementation(() => {})
- let counterRender = 0
- let observerRender = 0
-
- function Counter({ multiplier }: { multiplier: number }) {
- counterRender++
- const observableProps = useAsObservableSource({ multiplier })
-
- const store = useLocalStore(() => ({
- count: 10,
- get multiplied() {
- return observableProps.multiplier * this.count
- },
- inc() {
- this.count += 1
- }
- }))
-
- return (
-
- {() => {
- observerRender++
- return (
-
& React.RefAttributes
- >
- >
- : never /* forwardRef set for a non forwarding component */
- : C & { displayName: string }
// n.b. base case is not used for actual typings or exported in the typing files
export function observer
- | React.ForwardRefExoticComponent & React.RefAttributes>,
- // TODO remove in next major
- options?: IObserverOptions
+ | React.ForwardRefExoticComponent & React.RefAttributes>
) {
- if (process.env.NODE_ENV !== "production" && warnObserverOptionsDeprecated && options) {
- warnObserverOptionsDeprecated = false
- console.warn(
- `[mobx-react-lite] \`observer(fn, { forwardRef: true })\` is deprecated, use \`observer(React.forwardRef(fn))\``
- )
- }
-
if (ReactMemoSymbol && baseComponent["$$typeof"] === ReactMemoSymbol) {
throw new Error(
`[mobx-react-lite] You are trying to use \`observer\` on a function component wrapped in either another \`observer\` or \`React.memo\`. The observer already applies 'React.memo' for you.`
@@ -100,7 +50,7 @@ export function observer
(
return baseComponent
}
- let useForwardRef = options?.forwardRef ?? false
+ let useForwardRef = false
let render = baseComponent
const baseComponentName = baseComponent.displayName || baseComponent.name
@@ -132,20 +82,6 @@ export function observer
(
})
}
- // Support legacy context: `contextTypes` must be applied before `memo`
- if ((baseComponent as any).contextTypes) {
- ;(observerComponent as React.FunctionComponent).contextTypes = (
- baseComponent as any
- ).contextTypes
-
- if (process.env.NODE_ENV !== "production" && warnLegacyContextTypes) {
- warnLegacyContextTypes = false
- console.warn(
- `[mobx-react-lite] Support for Legacy Context in function components will be removed in the next major release.`
- )
- }
- }
-
if (useForwardRef) {
// `forwardRef` must be applied prior `memo`
// `forwardRef(observer(cmp))` throws:
@@ -160,18 +96,6 @@ export function observer
(
copyStaticProperties(baseComponent, observerComponent)
- if ("production" !== process.env.NODE_ENV) {
- Object.defineProperty(observerComponent, "contextTypes", {
- set() {
- throw new Error(
- `[mobx-react-lite] \`${
- this.displayName || this.type?.displayName || this.type?.name || "Component"
- }.contextTypes\` must be set before applying \`observer\`.`
- )
- }
- })
- }
-
return observerComponent
}
diff --git a/packages/mobx-react-lite/src/useAsObservableSource.ts b/packages/mobx-react-lite/src/useAsObservableSource.ts
deleted file mode 100644
index ffda077f68..0000000000
--- a/packages/mobx-react-lite/src/useAsObservableSource.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { useDeprecated } from "./utils/utils"
-import { observable, runInAction } from "mobx"
-import { useState } from "react"
-
-export function useAsObservableSource(current: TSource): TSource {
- if ("production" !== process.env.NODE_ENV)
- useDeprecated(
- "[mobx-react-lite] 'useAsObservableSource' is deprecated, please store the values directly in an observable, for example by using 'useLocalObservable', and sync future updates using 'useEffect' when needed. See the README for examples."
- )
- // We're deliberately not using idiomatic destructuring for the hook here.
- // Accessing the state value as an array element prevents TypeScript from generating unnecessary helpers in the resulting code.
- // For further details, please refer to mobxjs/mobx#3842.
- const res = useState(() => observable(current, {}, { deep: false }))[0]
- runInAction(() => {
- Object.assign(res, current)
- })
- return res
-}
diff --git a/packages/mobx-react-lite/src/useLocalObservable.ts b/packages/mobx-react-lite/src/useLocalObservable.ts
index fde49eac61..665d3d2b25 100644
--- a/packages/mobx-react-lite/src/useLocalObservable.ts
+++ b/packages/mobx-react-lite/src/useLocalObservable.ts
@@ -1,7 +1,7 @@
import { observable, AnnotationsMap } from "mobx"
import { useState } from "react"
-export function useLocalObservable>(
+export function useLocalObservable(
initializer: () => TStore,
annotations?: AnnotationsMap
): TStore {
diff --git a/packages/mobx-react-lite/src/useLocalStore.ts b/packages/mobx-react-lite/src/useLocalStore.ts
deleted file mode 100644
index 9060ee1110..0000000000
--- a/packages/mobx-react-lite/src/useLocalStore.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { observable } from "mobx"
-import { useState } from "react"
-
-import { useDeprecated } from "./utils/utils"
-import { useAsObservableSource } from "./useAsObservableSource"
-
-export function useLocalStore>(initializer: () => TStore): TStore
-export function useLocalStore, TSource extends object>(
- initializer: (source: TSource) => TStore,
- current: TSource
-): TStore
-export function useLocalStore, TSource extends object>(
- initializer: (source?: TSource) => TStore,
- current?: TSource
-): TStore {
- if ("production" !== process.env.NODE_ENV) {
- useDeprecated(
- "[mobx-react-lite] 'useLocalStore' is deprecated, use 'useLocalObservable' instead."
- )
- }
- const source = current && useAsObservableSource(current)
- return useState(() => observable(initializer(source), undefined, { autoBind: true }))[0]
-}
diff --git a/packages/mobx-react-lite/src/useObserver.ts b/packages/mobx-react-lite/src/useObserver.ts
index 886073467f..08da5b53fe 100644
--- a/packages/mobx-react-lite/src/useObserver.ts
+++ b/packages/mobx-react-lite/src/useObserver.ts
@@ -1,9 +1,7 @@
-import { Reaction } from "mobx"
+import { getDependencyTree, Reaction } from "mobx"
import React from "react"
-import { printDebugValue } from "./utils/printDebugValue"
import { isUsingStaticRendering } from "./staticRendering"
import { observerFinalizationRegistry } from "./utils/observerFinalizationRegistry"
-import { useSyncExternalStore } from "use-sync-external-store/shim"
// Do not store `admRef` (even as part of a closure!) on this object,
// otherwise it will prevent GC and therefore reaction disposal via FinalizationRegistry.
@@ -89,9 +87,9 @@ export function useObserver(render: () => T, baseComponentName: string = "obs
observerFinalizationRegistry.register(admRef, adm, adm)
}
- React.useDebugValue(adm.reaction!, printDebugValue)
+ React.useDebugValue(adm.reaction!, getDependencyTree)
- useSyncExternalStore(
+ React.useSyncExternalStore(
// Both of these must be stable, otherwise it would keep resubscribing every render.
adm.subscribe,
adm.getSnapshot,
diff --git a/packages/mobx-react-lite/src/utils/assertEnvironment.ts b/packages/mobx-react-lite/src/utils/assertEnvironment.ts
index 339dfb29d2..4fa7aad31d 100644
--- a/packages/mobx-react-lite/src/utils/assertEnvironment.ts
+++ b/packages/mobx-react-lite/src/utils/assertEnvironment.ts
@@ -1,9 +1,9 @@
-import { makeObservable } from "mobx"
-import { useState } from "react"
+import { _getGlobalState } from "mobx"
+import { useState, useSyncExternalStore } from "react"
-if (!useState) {
- throw new Error("mobx-react-lite requires React with Hooks support")
+if (!useState || !useSyncExternalStore) {
+ throw new Error("mobx-react-lite requires React 18 or later")
}
-if (!makeObservable) {
- throw new Error("mobx-react-lite@3 requires mobx at least version 6 to be available")
+if (!(_getGlobalState?.()?.version >= 7)) {
+ throw new Error("mobx-react-lite requires mobx at least version 7 to be available")
}
diff --git a/packages/mobx-react-lite/src/utils/observerBatching.ts b/packages/mobx-react-lite/src/utils/observerBatching.ts
deleted file mode 100644
index 42ce876251..0000000000
--- a/packages/mobx-react-lite/src/utils/observerBatching.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { configure } from "mobx"
-
-export function defaultNoopBatch(callback: () => void) {
- callback()
-}
-
-export function observerBatching(reactionScheduler: any) {
- if (!reactionScheduler) {
- reactionScheduler = defaultNoopBatch
- if ("production" !== process.env.NODE_ENV) {
- console.warn(
- "[MobX] Failed to get unstable_batched updates from react-dom / react-native"
- )
- }
- }
- configure({ reactionScheduler })
-}
-
-export const isObserverBatched = () => {
- if ("production" !== process.env.NODE_ENV) {
- console.warn("[MobX] Deprecated")
- }
-
- return true
-}
diff --git a/packages/mobx-react-lite/src/utils/printDebugValue.ts b/packages/mobx-react-lite/src/utils/printDebugValue.ts
deleted file mode 100644
index 8ef487fd64..0000000000
--- a/packages/mobx-react-lite/src/utils/printDebugValue.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { getDependencyTree, Reaction } from "mobx"
-
-export function printDebugValue(v: Reaction) {
- return getDependencyTree(v)
-}
diff --git a/packages/mobx-react-lite/src/utils/reactBatchedUpdates.native.ts b/packages/mobx-react-lite/src/utils/reactBatchedUpdates.native.ts
deleted file mode 100644
index a8e25fbcf7..0000000000
--- a/packages/mobx-react-lite/src/utils/reactBatchedUpdates.native.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// @ts-ignore
-export { unstable_batchedUpdates } from "react-native"
diff --git a/packages/mobx-react-lite/src/utils/reactBatchedUpdates.ts b/packages/mobx-react-lite/src/utils/reactBatchedUpdates.ts
deleted file mode 100644
index 8c64462b07..0000000000
--- a/packages/mobx-react-lite/src/utils/reactBatchedUpdates.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { unstable_batchedUpdates } from "react-dom"
diff --git a/packages/mobx-react-lite/tsconfig.build.cjs.json b/packages/mobx-react-lite/tsconfig.build.cjs.json
deleted file mode 100644
index 1bceb718ed..0000000000
--- a/packages/mobx-react-lite/tsconfig.build.cjs.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extends": "./tsconfig.build.json",
- "compilerOptions": {
- "outDir": "lib",
- "module": "CommonJS"
- }
-}
diff --git a/packages/mobx-react-lite/tsconfig.build.es.json b/packages/mobx-react-lite/tsconfig.build.es.json
deleted file mode 100644
index 1561876960..0000000000
--- a/packages/mobx-react-lite/tsconfig.build.es.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extends": "./tsconfig.build.json",
- "compilerOptions": {
- "outDir": "es",
- "module": "ESNext"
- }
-}
diff --git a/packages/mobx-react-lite/tsconfig.build.json b/packages/mobx-react-lite/tsconfig.build.json
deleted file mode 100644
index 5687524039..0000000000
--- a/packages/mobx-react-lite/tsconfig.build.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "esModuleInterop": true,
- "target": "ES5",
- "noEmit": false,
- "declaration": false
- }
-}
diff --git a/packages/mobx-react-lite/tsdx.config.js b/packages/mobx-react-lite/tsdx.config.js
deleted file mode 100644
index f2cf8dd75a..0000000000
--- a/packages/mobx-react-lite/tsdx.config.js
+++ /dev/null
@@ -1,15 +0,0 @@
-module.exports = {
- rollup(config) {
- return {
- ...config,
- output: {
- ...config.output,
- globals: {
- react: "React",
- mobx: "mobx",
- "react-dom": "ReactDOM"
- }
- }
- }
- }
-}
diff --git a/packages/mobx-react/CHANGELOG.md b/packages/mobx-react/CHANGELOG.md
index aa4a34cf6b..68a0a12fcd 100644
--- a/packages/mobx-react/CHANGELOG.md
+++ b/packages/mobx-react/CHANGELOG.md
@@ -1,5 +1,78 @@
# mobx-react
+## 10.0.0
+
+### Major Changes
+
+- [`1926c69f53168619d688f348aa587e8f3ae579ae`](https://github.com/mobxjs/mobx/commit/1926c69f53168619d688f348aa587e8f3ae579ae) [#4671](https://github.com/mobxjs/mobx/pull/4671) Thanks [@kubk](https://github.com/kubk)! - 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 ``.
+ - `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.
+
+### Patch Changes
+
+- Updated dependencies [[`1926c69f53168619d688f348aa587e8f3ae579ae`](https://github.com/mobxjs/mobx/commit/1926c69f53168619d688f348aa587e8f3ae579ae)]:
+ - mobx-react-lite@5.0.0
+
## 9.2.2
### Patch Changes
diff --git a/packages/mobx-react/README.md b/packages/mobx-react/README.md
index 4bdf704af9..387fbe90ff 100644
--- a/packages/mobx-react/README.md
+++ b/packages/mobx-react/README.md
@@ -17,21 +17,20 @@ Only the latest version is actively maintained. If you're missing a fix or a fea
| NPM Version | Support MobX version | Supported React versions | Added support for: |
| ----------- | -------------------- | ------------------------ | -------------------------------------------------------------------------------- |
+| v10 | 7.\* | >=18 | MobX 7, Hooks, React 18 strict mode |
| v9 | 6.\* | >16.8 | Hooks, React 18.2 in strict mode |
| v7 | 6.\* | >16.8 < 18.2 | Hooks |
| v6 | 4.\* / 5.\* | >16.8 <17 | Hooks |
| v5 | 4.\* / 5.\* | >0.13 <17 | No, but it is possible to use `` sections inside hook based components |
-mobx-react 6 / 7 is a repackage of the smaller [mobx-react-lite](https://github.com/mobxjs/mobx/tree/main/packages/mobx-react-lite) package + following features from the `mobx-react@5` package added:
+`mobx-react` is a wrapper around `mobx-react-lite` for applications that also need class component support:
+- Support for function components through `mobx-react-lite`
- Support for class based components for `observer` and `@observer`
-- `Provider / inject` to pass stores around (but consider to use `React.createContext` instead)
-- `PropTypes` to describe observable based property checkers (but consider to use TypeScript instead)
-- The `disposeOnUnmount` utility / decorator to easily clean up resources such as reactions created in your class based components.
## Installation
-`npm install mobx-react --save`
+`npm install mobx-react`
Or CDN: https://unpkg.com/mobx-react (UMD namespace: `mobxReact`)
@@ -42,7 +41,7 @@ import { observer } from "mobx-react"
This package provides the bindings for MobX and React.
See the [official documentation](https://mobx.js.org/react-integration.html) for how to get started.
-For greenfield projects you might want to consider to use [mobx-react-lite](https://github.com/mobxjs/mobx/tree/main/packages/mobx-react-lite), if you intend to only use function based components. `React.createContext` can be used to pass stores around.
+Use `React.createContext` to pass stores around.
## API documentation
@@ -226,7 +225,7 @@ person.name = "Mike" // will cause the Observer region to re-render
Local observable state can be introduced by using the `useLocalObservable` hook, that runs once to create an observable store. A quick example would be:
```javascript
-import { useLocalObservable, Observer } from "mobx-react-lite"
+import { useLocalObservable, Observer } from "mobx-react"
const Todo = () => {
const todo = useLocalObservable(() => ({
@@ -241,7 +240,7 @@ const Todo = () => {
{() => (
)}
@@ -253,7 +252,7 @@ When using `useLocalObservable`, all properties of the returned object will be m
It is important to realize that the store is created only once! It is not possible to specify dependencies to force re-creation, _nor should you directly be referring to props for the initializer function_, as changes in those won't propagate.
-Instead, if your store needs to refer to props (or `useState` based local state), the `useLocalObservable` should be combined with the `useAsObservableSource` hook, see below.
+Instead, if your store needs to refer to props (or `useState` based local state), sync those values into the store with `useEffect`.
Note that in many cases it is possible to extract the initializer function to a function outside the component definition. Which makes it possible to test the store itself in a more straight-forward manner, and avoids creating the initializer closure on each re-render.
@@ -287,261 +286,6 @@ Decorators are currently a stage-2 ESNext feature. How to enable them is documen
See this [thread](https://www.reddit.com/r/reactjs/comments/4vnxg5/free_eggheadio_course_learn_mobx_react_in_30/d61oh0l).
TL;DR: the conceptual distinction makes a lot of sense when using MobX as well, but use `observer` on all components.
-### `PropTypes`
-
-MobX-react provides the following additional `PropTypes` which can be used to validate against MobX structures:
-
-- `observableArray`
-- `observableArrayOf(React.PropTypes.number)`
-- `observableMap`
-- `observableObject`
-- `arrayOrObservableArray`
-- `arrayOrObservableArrayOf(React.PropTypes.number)`
-- `objectOrObservableObject`
-
-Use `import { PropTypes } from "mobx-react"` to import them, then use for example `PropTypes.observableArray`
-
-### `Provider` and `inject`
-
-_Note: usually there is no need anymore to use `Provider` / `inject` in new code bases; most of its features are now covered by `React.createContext`._
-
-`Provider` is a component that can pass stores (or other stuff) using React's context mechanism to child components.
-This is useful if you have things that you don't want to pass through multiple layers of components explicitly.
-
-`inject` can be used to pick up those stores. It is a higher order component that takes a list of strings and makes those stores available to the wrapped component.
-
-Example (based on the official [context docs](https://facebook.github.io/react/docs/context.html#passing-info-automatically-through-a-tree)):
-
-```javascript
-@inject("color")
-@observer
-class Button extends React.Component {
- render() {
- return
- }
-}
-
-class Message extends React.Component {
- render() {
- return (
-
-
- )
- }
-}
-```
-
-Notes:
-
-- It is possible to read the stores provided by `Provider` using `React.useContext`, by using the `MobXProviderContext` context that can be imported from `mobx-react`.
-- If a component asks for a store and receives a store via a property with the same name, the property takes precedence. Use this to your advantage when testing!
-- When using both `@inject` and `@observer`, make sure to apply them in the correct order: `observer` should be the inner decorator, `inject` the outer. There might be additional decorators in between.
-- The original component wrapped by `inject` is available as the `wrappedComponent` property of the created higher order component.
-
-#### "The set of provided stores has changed" error
-
-Values provided through `Provider` should be final. Make sure that if you put things in `context` that might change over time, that they are `@observable` or provide some other means to listen to changes, like callbacks. However, if your stores will change over time, like an observable value of another store, MobX will throw an error.
-This restriction exists mainly for legacy reasons. If you have a scenario where you need to modify the set of stores, please leave a comment about it in this issue https://github.com/mobxjs/mobx-react/issues/745. Or a preferred way is to [use React Context](https://reactjs.org/docs/context.html) directly which does not have this restriction.
-
-#### Inject as function
-
-The above example in ES5 would start like:
-
-```javascript
-var Button = inject("color")(
- observer(
- class Button extends Component {
- /* ... etc ... */
- }
- )
-)
-```
-
-A functional stateless component would look like:
-
-```javascript
-var Button = inject("color")(
- observer(({ color }) => {
- /* ... etc ... */
- })
-)
-```
-
-#### Customizing inject
-
-Instead of passing a list of store names, it is also possible to create a custom mapper function and pass it to inject.
-The mapper function receives all stores as argument, the properties with which the components are invoked and the context, and should produce a new set of properties,
-that are mapped into the original:
-
-`mapperFunction: (allStores, props, context) => additionalProps`
-
-Since version 4.0 the `mapperFunction` itself is tracked as well, so it is possible to do things like:
-
-```javascript
-const NameDisplayer = ({ name }) =>
{name}
-
-const UserNameDisplayer = inject(stores => ({
- name: stores.userStore.name
-}))(NameDisplayer)
-
-const user = mobx.observable({
- name: "Noa"
-})
-
-const App = () => (
-
-
-
-)
-
-ReactDOM.render(, document.body)
-```
-
-_N.B. note that in this *specific* case neither `NameDisplayer` nor `UserNameDisplayer` needs to be decorated with `observer`, since the observable dereferencing is done in the mapper function_
-
-#### Using `PropTypes` and `defaultProps` and other static properties in combination with `inject`
-
-Inject wraps a new component around the component you pass into it.
-This means that assigning a static property to the resulting component, will be applied to the HoC, and not to the original component.
-So if you take the following example:
-
-```javascript
-const UserName = inject("userStore")(({ userStore, bold }) => someRendering())
-
-UserName.propTypes = {
- bold: PropTypes.boolean.isRequired,
- userStore: PropTypes.object.isRequired // will always fail
-}
-```
-
-The above propTypes are incorrect, `bold` needs to be provided by the caller of the `UserName` component and is checked by React.
-However, `userStore` does not need to be required! Although it is required for the original stateless function component, it is not
-required for the resulting inject component. After all, the whole point of that component is to provide that `userStore` itself.
-
-So if you want to make assertions on the data that is being injected (either stores or data resulting from a mapper function), the propTypes
-should be defined on the _wrapped_ component. Which is available through the static property `wrappedComponent` on the inject component:
-
-```javascript
-const UserName = inject("userStore")(({ userStore, bold }) => someRendering())
-
-UserName.propTypes = {
- bold: PropTypes.boolean.isRequired // could be defined either here ...
-}
-
-UserName.wrappedComponent.propTypes = {
- // ... or here
- userStore: PropTypes.object.isRequired // correct
-}
-```
-
-The same principle applies to `defaultProps` and other static React properties.
-Note that it is not allowed to redefine `contextTypes` on `inject` components (but is possible to define it on `wrappedComponent`)
-
-Finally, mobx-react will automatically move non React related static properties from wrappedComponent to the inject component so that all static fields are
-actually available to the outside world without needing `.wrappedComponent`.
-
-#### Strongly typing inject
-
-##### With TypeScript
-
-`inject` also accepts a function (`(allStores, nextProps, nextContext) => additionalProps`) that can be used to pick all the desired stores from the available stores like this.
-The `additionalProps` will be merged into the original `nextProps` before being provided to the next component.
-
-```typescript
-import { IUserStore } from "myStore"
-
-@inject(allStores => ({
- userStore: allStores.userStore as IUserStore
-}))
-class MyComponent extends React.Component<{ userStore?: IUserStore; otherProp: number }, {}> {
- /* etc */
-}
-```
-
-Make sure to mark `userStore` as an optional property. It should not (necessarily) be passed in by parent components at all!
-
-Note: If you have strict null checking enabled, you could muffle the nullable type by using the `!` operator:
-
-```
-public render() {
- const {a, b} = this.store!
- // ...
-}
-```
-
-#### Testing store injection
-
-It is allowed to pass any declared store in directly as a property as well. This makes it easy to set up individual component tests without a provider.
-
-So if you have in your app something like:
-
-```javascript
-
-
-
-```
-
-In your test you can easily test the `Person` component by passing the necessary store as prop directly:
-
-```
-const profile = new Profile()
-const mountedComponent = mount(
-
-)
-```
-
-Bear in mind that using shallow rendering won't provide any useful results when testing injected components; only the injector will be rendered.
-To test with shallow rendering, instantiate the `wrappedComponent` instead: `shallow()`
-
-### disposeOnUnmount(componentInstance, propertyKey | function | function[])
-
-Function (and decorator) that makes sure a function (usually a disposer such as the ones returned by `reaction`, `autorun`, etc.) is automatically executed as part of the componentWillUnmount lifecycle event.
-
-```javascript
-import { disposeOnUnmount } from "mobx-react"
-
-class SomeComponent extends React.Component {
- // decorator version
- @disposeOnUnmount
- someReactionDisposer = reaction(...)
-
- // decorator version with arrays
- @disposeOnUnmount
- someReactionDisposers = [
- reaction(...),
- reaction(...)
- ]
-
-
- // function version over properties
- someReactionDisposer = disposeOnUnmount(this, reaction(...))
-
- // function version inside methods
- componentDidMount() {
- // single function
- disposeOnUnmount(this, reaction(...))
-
- // or function array
- disposeOnUnmount(this, [
- reaction(...),
- reaction(...)
- ])
- }
-}
-```
-
## DevTools
`mobx-react@6` and higher are no longer compatible with the mobx-react-devtools.
diff --git a/packages/mobx-react/__tests__/ObserverComponent.test.tsx b/packages/mobx-react/__tests__/ObserverComponent.test.tsx
new file mode 100644
index 0000000000..0f80fe8321
--- /dev/null
+++ b/packages/mobx-react/__tests__/ObserverComponent.test.tsx
@@ -0,0 +1,42 @@
+import mockConsole from "jest-mock-console"
+import * as mobx from "mobx"
+import * as React from "react"
+import { act, render } from "@testing-library/react"
+
+import { Observer } from "../src"
+
+describe("regions should rerender component", () => {
+ const execute = () => {
+ const data = mobx.observable.box("hi")
+ const Comp = () => (
+
+ {() => {data.get()}}
+
{data.get()}
+
+ )
+ return { ...render(), data }
+ }
+
+ test("init state is correct", () => {
+ const { container } = execute()
+ expect(container.querySelector("span")!.innerHTML).toBe("hi")
+ expect(container.querySelector("li")!.innerHTML).toBe("hi")
+ })
+
+ test("set the data to hello", async () => {
+ const { container, data } = execute()
+ act(() => {
+ data.set("hello")
+ })
+ expect(container.querySelector("span")!.innerHTML).toBe("hello")
+ expect(container.querySelector("li")!.innerHTML).toBe("hi")
+ })
+})
+
+it("renders null if no children/render prop is supplied a function", () => {
+ const restoreConsole = mockConsole()
+ const Comp = () =>
+ const { container } = render()
+ expect(container).toMatchInlineSnapshot(``)
+ restoreConsole()
+})
diff --git a/packages/mobx-react/__tests__/Provider.test.tsx b/packages/mobx-react/__tests__/Provider.test.tsx
deleted file mode 100644
index effea7a52f..0000000000
--- a/packages/mobx-react/__tests__/Provider.test.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import React from "react"
-import { Provider } from "../src"
-import { render } from "@testing-library/react"
-import { MobXProviderContext } from "../src/Provider"
-import { withConsole } from "./utils/withConsole"
-
-describe("Provider", () => {
- it("should work in a simple case", () => {
- function A() {
- return (
-
- {({ foo }) => foo}
-
- )
- }
-
- const { container } = render()
- expect(container).toHaveTextContent("bar")
- })
-
- it("should not provide the children prop", () => {
- function A() {
- return (
-
-
- {stores =>
- Reflect.has(stores, "children")
- ? "children was provided"
- : "children was not provided"
- }
-
-
- )
- }
-
- const { container } = render()
- expect(container).toHaveTextContent("children was not provided")
- })
-
- it("supports overriding stores", () => {
- function B() {
- return (
-
- {({ overridable, nonOverridable }) => `${overridable} ${nonOverridable}`}
-
- )
- }
-
- function A() {
- return (
-
-
-
-
-
-
- )
- }
- const { container } = render()
- expect(container).toMatchInlineSnapshot(`
-
- original original
- overridden original
-
-`)
- })
-
- it("should throw an error when changing stores", () => {
- function A({ foo }) {
- return (
-
- {({ foo }) => foo}
-
- )
- }
-
- const { rerender } = render()
-
- withConsole(() => {
- expect(() => {
- rerender()
- }).toThrow("The set of provided stores has changed.")
- })
- })
-})
diff --git a/packages/mobx-react/__tests__/__snapshots__/hooks.test.tsx.snap b/packages/mobx-react/__tests__/__snapshots__/hooks.test.tsx.snap
deleted file mode 100644
index 9b95cca6aa..0000000000
--- a/packages/mobx-react/__tests__/__snapshots__/hooks.test.tsx.snap
+++ /dev/null
@@ -1,24 +0,0 @@
-// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
-
-exports[`computed properties react to props when using hooks 1`] = `
-[MockFunction] {
- "calls": [
- [
- "[mobx-react-lite] 'useAsObservableSource' is deprecated, please store the values directly in an observable, for example by using 'useLocalObservable', and sync future updates using 'useEffect' when needed. See the README for examples.",
- ],
- [
- "[mobx-react-lite] 'useLocalStore' is deprecated, use 'useLocalObservable' instead.",
- ],
- ],
- "results": [
- {
- "type": "return",
- "value": undefined,
- },
- {
- "type": "return",
- "value": undefined,
- },
- ],
-}
-`;
diff --git a/packages/mobx-react/__tests__/api.test.tsx b/packages/mobx-react/__tests__/api.test.tsx
new file mode 100644
index 0000000000..d14bd4138e
--- /dev/null
+++ b/packages/mobx-react/__tests__/api.test.tsx
@@ -0,0 +1,19 @@
+const api = require("../src/index.ts")
+
+test("correct api should be exposed", function () {
+ expect(
+ Object.keys(api)
+ .filter(key => api[key] !== undefined)
+ .sort()
+ ).toEqual(
+ [
+ "isUsingStaticRendering",
+ "enableStaticRendering",
+ "observer",
+ "Observer",
+ "useLocalObservable",
+ "clearTimers",
+ "_observerFinalizationRegistry"
+ ].sort()
+ )
+})
diff --git a/packages/mobx-react/__tests__/context.test.tsx b/packages/mobx-react/__tests__/context.test.tsx
deleted file mode 100644
index e88dbda85d..0000000000
--- a/packages/mobx-react/__tests__/context.test.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import React from "react"
-import { observable } from "mobx"
-import { Provider, observer, inject } from "../src"
-import { withConsole } from "./utils/withConsole"
-import { render, act } from "@testing-library/react"
-import { any } from "prop-types"
-
-test("no warnings in modern react", () => {
- const box = observable.box(3)
- const Child = inject("store")(
- observer(
- class Child extends React.Component {
- render() {
- return (
-
- )
- }
- }
+test("MobX computed getters cannot read class props", () => {
+ const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {})
- test("init state is correct", () => {
- const { container } = render()
+ const Comp = observer(
+ class TestCmp extends React.Component<{ x: number }> {
+ constructor(props) {
+ super(props)
+ makeObservable(this, {
+ computedProp: computed
+ })
+ }
- expect(container).toHaveTextContent("x:1")
- })
+ get computedProp() {
+ return this.props.x
+ }
- test("change after click", () => {
- const { container } = render()
+ render() {
+ return {this.computedProp}
+ }
+ }
+ )
- act(() => container.querySelector("div")!.click())
- expect(container).toHaveTextContent("x:2")
- })
+ try {
+ expect(() => render()).toThrow(
+ /^\[mobx-react\] Cannot read "TestCmp.props" in a reactive context/
+ )
+ } finally {
+ consoleErrorSpy.mockRestore()
+ }
})
-// Test on skip: since all reactions are now run in batched updates, the original issues can no longer be reproduced
-//this test case should be deprecated?
test("should stop updating if error was thrown in render (#134)", () => {
const data = observable.box(0)
let renderingsCount = 0
@@ -517,21 +464,16 @@ describe("should render component even if setState called with exactly the same
})
})
-test("it rerenders correctly if some props are non-observables - 1", () => {
+test("class observer rerenders from observable prop and rereads plain prop", () => {
let odata = observable({ x: 1 })
let data = { y: 1 }
@observer
class Comp extends React.Component {
- @computed
- get computed() {
- // n.b: data.y would not rerender! shallowly new equal props are not stored
- return this.props.odata.x
- }
render() {
return (
- {this.props.odata.x}-{this.props.data.y}-{this.computed}
+ {this.props.odata.x}-{this.props.data.y}-{this.props.odata.x}
)
}
@@ -540,7 +482,6 @@ test("it rerenders correctly if some props are non-observables - 1", () => {
const Parent = observer(
class Parent extends React.Component {
render() {
- // this.props.odata.x;
return
}
}
@@ -562,50 +503,6 @@ test("it rerenders correctly if some props are non-observables - 1", () => {
expect(container).toHaveTextContent("3-3-3")
})
-test("it rerenders correctly if some props are non-observables - 2", () => {
- let renderCount = 0
- let odata = observable({ x: 1 })
-
- @observer
- class Component extends React.PureComponent {
- @computed
- get computed() {
- return this.props.data.y // should recompute, since props.data is changed
- }
-
- render() {
- renderCount++
- return (
-
- {this.props.data.y}-{this.computed}
-
- )
- }
- }
-
- const Parent = observer(props => {
- let data = { y: props.odata.x }
- return
- })
-
- function stuff() {
- odata.x++
- }
-
- const { container } = render()
-
- expect(renderCount).toBe(1)
- expect(container).toHaveTextContent("1-1")
-
- act(() => stuff())
- expect(renderCount).toBe(2)
- expect(container).toHaveTextContent("2-2")
-
- act(() => stuff())
- expect(renderCount).toBe(3)
- expect(container).toHaveTextContent("3-3")
-})
-
describe("Observer regions should react", () => {
let data
const Comp = () => (
@@ -671,28 +568,34 @@ test("parent / childs render in the right order", () => {
let events: Array = []
class User {
- @observable
name = "User's name"
+
+ constructor() {
+ makeObservable(this, {
+ name: observable
+ })
+ }
}
class Store {
- @observable
user: User | null = new User()
- @action
+
logout() {
this.user = null
}
+
constructor() {
- makeObservable(this)
+ makeObservable(this, {
+ user: observable,
+ logout: action
+ })
}
}
function tryLogout() {
try {
- // ReactDOM.unstable_batchedUpdates(() => {
store.logout()
expect(true).toBeTruthy()
- // });
} catch (e) {
// t.fail(e)
}
@@ -722,8 +625,8 @@ test("parent / childs render in the right order", () => {
expect(events).toEqual(["parent", "child", "parent"])
})
-describe("use Observer inject and render sugar should work ", () => {
- test("use render without inject should be correct", () => {
+describe("Observer render sugar should work", () => {
+ test("use render should be correct", () => {
const Comp = () => (
{123}} />
@@ -733,7 +636,7 @@ describe("use Observer inject and render sugar should work ", () => {
expect(container).toHaveTextContent("123")
})
- test("use children without inject should be correct", () => {
+ test("use children should be correct", () => {
const Comp = () => (
{() => {123}}
@@ -750,6 +653,7 @@ describe("use Observer inject and render sugar should work ", () => {
const Comp = () => (
+ {/* @ts-expect-error render and children are mutually exclusive */}
{123}}>{() => {123}}
)
@@ -789,46 +693,6 @@ test("static on function components are hoisted", () => {
expect(Comp2.foo).toBe(3)
})
-test("computed properties react to props", () => {
- jest.useFakeTimers()
-
- const seen: Array = []
- @observer
- class Child extends React.Component {
- @computed
- get getPropX() {
- return this.props.x
- }
-
- render() {
- seen.push(this.getPropX)
- return