From 75a0890cb893a7eb41c12a845f460b026ae900e0 Mon Sep 17 00:00:00 2001 From: Venryx Date: Sun, 9 Mar 2025 12:50:23 -0700 Subject: [PATCH 1/4] * Removed propTypes.ts, as React 19 no longer provides the types React.Requireable and React.Validator. This should resolve the remaining typescript conflicts that mobx-react had with React 19. * Removed the "component with observable propTypes" test, since it's no longer applicable. (and it was referencing typescript types that are no longer importable, so using `test.skip` is not sufficient) --- .../mobx-react/__tests__/stateless.test.tsx | 25 +-- packages/mobx-react/src/index.ts | 1 - packages/mobx-react/src/propTypes.ts | 210 ------------------ 3 files changed, 1 insertion(+), 235 deletions(-) delete mode 100644 packages/mobx-react/src/propTypes.ts diff --git a/packages/mobx-react/__tests__/stateless.test.tsx b/packages/mobx-react/__tests__/stateless.test.tsx index febffeece3..1ecbf4a06c 100644 --- a/packages/mobx-react/__tests__/stateless.test.tsx +++ b/packages/mobx-react/__tests__/stateless.test.tsx @@ -1,6 +1,6 @@ import React from "react" import PropTypes from "prop-types" -import { observer, PropTypes as MRPropTypes } from "../src" +import { observer } from "../src" import { render, act } from "@testing-library/react" import { observable } from "mobx" @@ -58,29 +58,6 @@ test("stateless component with context support", () => { expect(container.textContent).toBe("context: hello world") }) -// propTypes validation seems to have been removed from class components in React 19: https://react.dev/reference/react/Component -test.skip("component with observable propTypes", () => { - class Comp extends React.Component { - render() { - return null - } - static propTypes = { - a1: MRPropTypes.observableArray, - a2: MRPropTypes.arrayOrObservableArray - } - } - const originalConsoleError = console.error - const warnings: Array = [] - console.error = msg => warnings.push(msg) - // eslint-disable-next-line no-unused-vars - const firstWrapper = - expect(warnings.length).toBe(1) - // eslint-disable-next-line no-unused-vars - const secondWrapper = - expect(warnings.length).toBe(1) - console.error = originalConsoleError -}) - describe("stateless component with forwardRef", () => { const a = observable({ x: 1 diff --git a/packages/mobx-react/src/index.ts b/packages/mobx-react/src/index.ts index 65abc7ed72..a6a6fe3fcc 100644 --- a/packages/mobx-react/src/index.ts +++ b/packages/mobx-react/src/index.ts @@ -26,5 +26,4 @@ export { observer } from "./observer" export { MobXProviderContext, Provider, ProviderProps } from "./Provider" export { inject } from "./inject" export { disposeOnUnmount } from "./disposeOnUnmount" -export { PropTypes } from "./propTypes" export { IWrappedComponent } from "./types/IWrappedComponent" diff --git a/packages/mobx-react/src/propTypes.ts b/packages/mobx-react/src/propTypes.ts deleted file mode 100644 index 0e6e164bce..0000000000 --- a/packages/mobx-react/src/propTypes.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { isObservableArray, isObservableObject, isObservableMap, untracked } from "mobx" - -// Copied from React.PropTypes -function createChainableTypeChecker(validator: React.Validator): React.Requireable { - function checkType( - isRequired: boolean, - props: any, - propName: string, - componentName: string, - location: string, - propFullName: string, - ...rest: any[] - ) { - return untracked(() => { - componentName = componentName || "<>" - propFullName = propFullName || propName - if (props[propName] == null) { - if (isRequired) { - const actual = props[propName] === null ? "null" : "undefined" - return new Error( - "The " + - location + - " `" + - propFullName + - "` is marked as required " + - "in `" + - componentName + - "`, but its value is `" + - actual + - "`." - ) - } - return null - } else { - // @ts-ignore rest arg is necessary for some React internals - fails tests otherwise - return validator(props, propName, componentName, location, propFullName, ...rest) - } - }) - } - - const chainedCheckType: any = checkType.bind(null, false) - // Add isRequired to satisfy Requirable - chainedCheckType.isRequired = checkType.bind(null, true) - return chainedCheckType -} - -// Copied from React.PropTypes -function isSymbol(propType: any, propValue: any): boolean { - // Native Symbol. - if (propType === "symbol") { - return true - } - - // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol' - if (propValue["@@toStringTag"] === "Symbol") { - return true - } - - // Fallback for non-spec compliant Symbols which are polyfilled. - if (typeof Symbol === "function" && propValue instanceof Symbol) { - return true - } - - return false -} - -// Copied from React.PropTypes -function getPropType(propValue: any): string { - const propType = typeof propValue - if (Array.isArray(propValue)) { - return "array" - } - if (propValue instanceof RegExp) { - // Old webkits (at least until Android 4.0) return 'function' rather than - // 'object' for typeof a RegExp. We'll normalize this here so that /bla/ - // passes PropTypes.object. - return "object" - } - if (isSymbol(propType, propValue)) { - return "symbol" - } - return propType -} - -// This handles more types than `getPropType`. Only used for error messages. -// Copied from React.PropTypes -function getPreciseType(propValue: any): string { - const propType = getPropType(propValue) - if (propType === "object") { - if (propValue instanceof Date) { - return "date" - } else if (propValue instanceof RegExp) { - return "regexp" - } - } - return propType -} - -function createObservableTypeCheckerCreator( - allowNativeType: any, - mobxType: any -): React.Requireable { - return createChainableTypeChecker((props, propName, componentName, location, propFullName) => { - return untracked(() => { - if (allowNativeType) { - if (getPropType(props[propName]) === mobxType.toLowerCase()) return null - } - let mobxChecker - switch (mobxType) { - case "Array": - mobxChecker = isObservableArray - break - case "Object": - mobxChecker = isObservableObject - break - case "Map": - mobxChecker = isObservableMap - break - default: - throw new Error(`Unexpected mobxType: ${mobxType}`) - } - const propValue = props[propName] - if (!mobxChecker(propValue)) { - const preciseType = getPreciseType(propValue) - const nativeTypeExpectationMessage = allowNativeType - ? " or javascript `" + mobxType.toLowerCase() + "`" - : "" - return new Error( - "Invalid prop `" + - propFullName + - "` of type `" + - preciseType + - "` supplied to" + - " `" + - componentName + - "`, expected `mobx.Observable" + - mobxType + - "`" + - nativeTypeExpectationMessage + - "." - ) - } - return null - }) - }) -} - -function createObservableArrayOfTypeChecker( - allowNativeType: boolean, - typeChecker: React.Validator -) { - return createChainableTypeChecker( - (props, propName, componentName, location, propFullName, ...rest) => { - return untracked(() => { - if (typeof typeChecker !== "function") { - return new Error( - "Property `" + - propFullName + - "` of component `" + - componentName + - "` has " + - "invalid PropType notation." - ) - } else { - let error = createObservableTypeCheckerCreator(allowNativeType, "Array")( - props, - propName, - componentName, - location, - propFullName - ) - - if (error instanceof Error) return error - const propValue = props[propName] - for (let i = 0; i < propValue.length; i++) { - error = (typeChecker as React.Validator)( - propValue, - i as any, - componentName, - location, - propFullName + "[" + i + "]", - ...rest - ) - if (error instanceof Error) return error - } - - return null - } - }) - } - ) -} - -const observableArray = createObservableTypeCheckerCreator(false, "Array") -const observableArrayOf = createObservableArrayOfTypeChecker.bind(null, false) -const observableMap = createObservableTypeCheckerCreator(false, "Map") -const observableObject = createObservableTypeCheckerCreator(false, "Object") -const arrayOrObservableArray = createObservableTypeCheckerCreator(true, "Array") -const arrayOrObservableArrayOf = createObservableArrayOfTypeChecker.bind(null, true) -const objectOrObservableObject = createObservableTypeCheckerCreator(true, "Object") - -export const PropTypes = { - observableArray, - observableArrayOf, - observableMap, - observableObject, - arrayOrObservableArray, - arrayOrObservableArrayOf, - objectOrObservableObject -} From a11fab269d6f79459fbc5189e747fc5959130dbb Mon Sep 17 00:00:00 2001 From: Venryx Date: Sun, 9 Mar 2025 13:03:21 -0700 Subject: [PATCH 2/4] * Removed some remaining references to the (now removed) mobx-react specific prop-types. --- docs/react-integration.md | 1 - packages/mobx-react/README.md | 15 -- .../mobx-react/__tests__/propTypes.test.ts | 216 ------------------ .../mobx-react/__tests__/utils/compile-ts.tsx | 13 +- 4 files changed, 1 insertion(+), 244 deletions(-) delete mode 100644 packages/mobx-react/__tests__/propTypes.test.ts diff --git a/docs/react-integration.md b/docs/react-integration.md index 2bbd49aeea..7b9b1567e6 100644 --- a/docs/react-integration.md +++ b/docs/react-integration.md @@ -317,7 +317,6 @@ It offers a few more features which are typically not needed anymore in greenfie 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. diff --git a/packages/mobx-react/README.md b/packages/mobx-react/README.md index 4bdf704af9..4719876829 100644 --- a/packages/mobx-react/README.md +++ b/packages/mobx-react/README.md @@ -26,7 +26,6 @@ mobx-react 6 / 7 is a repackage of the smaller [mobx-react-lite](https://github. - 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 @@ -287,20 +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`._ diff --git a/packages/mobx-react/__tests__/propTypes.test.ts b/packages/mobx-react/__tests__/propTypes.test.ts deleted file mode 100644 index 6f5a7aa10d..0000000000 --- a/packages/mobx-react/__tests__/propTypes.test.ts +++ /dev/null @@ -1,216 +0,0 @@ -import PropTypes from "prop-types" -import { PropTypes as MRPropTypes } from "../src" -import { observable } from "mobx" - -// Cause `checkPropTypes` caches errors and doesn't print them twice.... -// https://github.com/facebook/prop-types/issues/91 -let testComponentId = 0 - -function typeCheckFail(declaration, value, message) { - const baseError = console.error - let error = "" - console.error = msg => { - error = msg - } - - const props = { testProp: value } - const propTypes = { testProp: declaration } - - const compId = "testComponent" + ++testComponentId - PropTypes.checkPropTypes(propTypes, props, "prop", compId) - - error = error.replace(compId, "testComponent") - expect(error).toBe("Warning: Failed prop type: " + message) - console.error = baseError -} - -function typeCheckFailRequiredValues(declaration) { - const baseError = console.error - let error = "" - console.error = msg => { - error = msg - } - - const propTypes = { testProp: declaration } - const specifiedButIsNullMsg = /but its value is `null`\./ - const unspecifiedMsg = /but its value is `undefined`\./ - - const props1 = { testProp: null } - PropTypes.checkPropTypes(propTypes, props1, "testProp", "testComponent" + ++testComponentId) - expect(specifiedButIsNullMsg.test(error)).toBeTruthy() - - error = "" - const props2 = { testProp: undefined } - PropTypes.checkPropTypes(propTypes, props2, "testProp", "testComponent" + ++testComponentId) - expect(unspecifiedMsg.test(error)).toBeTruthy() - - error = "" - const props3 = {} - PropTypes.checkPropTypes(propTypes, props3, "testProp", "testComponent" + ++testComponentId) - expect(unspecifiedMsg.test(error)).toBeTruthy() - - console.error = baseError -} - -function typeCheckPass(declaration: any, value?: any) { - const props = { testProp: value } - const error = PropTypes.checkPropTypes( - { testProp: declaration }, - props, - "testProp", - "testComponent" + ++testComponentId - ) - expect(error).toBeUndefined() -} - -test("Valid values", () => { - typeCheckPass(MRPropTypes.observableArray, observable([])) - typeCheckPass(MRPropTypes.observableArrayOf(PropTypes.string), observable([""])) - typeCheckPass(MRPropTypes.arrayOrObservableArray, observable([])) - typeCheckPass(MRPropTypes.arrayOrObservableArray, []) - typeCheckPass(MRPropTypes.arrayOrObservableArrayOf(PropTypes.string), observable([""])) - typeCheckPass(MRPropTypes.arrayOrObservableArrayOf(PropTypes.string), [""]) - typeCheckPass(MRPropTypes.observableObject, observable({})) - typeCheckPass(MRPropTypes.objectOrObservableObject, {}) - typeCheckPass(MRPropTypes.objectOrObservableObject, observable({})) - typeCheckPass(MRPropTypes.observableMap, observable(observable.map({}, { deep: false }))) -}) - -test("should be implicitly optional and not warn", () => { - typeCheckPass(MRPropTypes.observableArray) - typeCheckPass(MRPropTypes.observableArrayOf(PropTypes.string)) - typeCheckPass(MRPropTypes.arrayOrObservableArray) - typeCheckPass(MRPropTypes.arrayOrObservableArrayOf(PropTypes.string)) - typeCheckPass(MRPropTypes.observableObject) - typeCheckPass(MRPropTypes.objectOrObservableObject) - typeCheckPass(MRPropTypes.observableMap) -}) - -test("should warn for missing required values, function (test)", () => { - typeCheckFailRequiredValues(MRPropTypes.observableArray.isRequired) - typeCheckFailRequiredValues(MRPropTypes.observableArrayOf(PropTypes.string).isRequired) - typeCheckFailRequiredValues(MRPropTypes.arrayOrObservableArray.isRequired) - typeCheckFailRequiredValues(MRPropTypes.arrayOrObservableArrayOf(PropTypes.string).isRequired) - typeCheckFailRequiredValues(MRPropTypes.observableObject.isRequired) - typeCheckFailRequiredValues(MRPropTypes.objectOrObservableObject.isRequired) - typeCheckFailRequiredValues(MRPropTypes.observableMap.isRequired) -}) - -test("should fail date and regexp correctly", () => { - typeCheckFail( - MRPropTypes.observableObject, - new Date(), - "Invalid prop `testProp` of type `date` supplied to " + - "`testComponent`, expected `mobx.ObservableObject`." - ) - typeCheckFail( - MRPropTypes.observableArray, - /please/, - "Invalid prop `testProp` of type `regexp` supplied to " + - "`testComponent`, expected `mobx.ObservableArray`." - ) -}) - -test("observableArray", () => { - typeCheckFail( - MRPropTypes.observableArray, - [], - "Invalid prop `testProp` of type `array` supplied to " + - "`testComponent`, expected `mobx.ObservableArray`." - ) - typeCheckFail( - MRPropTypes.observableArray, - "", - "Invalid prop `testProp` of type `string` supplied to " + - "`testComponent`, expected `mobx.ObservableArray`." - ) -}) - -test("arrayOrObservableArray", () => { - typeCheckFail( - MRPropTypes.arrayOrObservableArray, - "", - "Invalid prop `testProp` of type `string` supplied to " + - "`testComponent`, expected `mobx.ObservableArray` or javascript `array`." - ) -}) - -test("observableObject", () => { - typeCheckFail( - MRPropTypes.observableObject, - {}, - "Invalid prop `testProp` of type `object` supplied to " + - "`testComponent`, expected `mobx.ObservableObject`." - ) - typeCheckFail( - MRPropTypes.observableObject, - "", - "Invalid prop `testProp` of type `string` supplied to " + - "`testComponent`, expected `mobx.ObservableObject`." - ) -}) - -test("objectOrObservableObject", () => { - typeCheckFail( - MRPropTypes.objectOrObservableObject, - "", - "Invalid prop `testProp` of type `string` supplied to " + - "`testComponent`, expected `mobx.ObservableObject` or javascript `object`." - ) -}) - -test("observableMap", () => { - typeCheckFail( - MRPropTypes.observableMap, - {}, - "Invalid prop `testProp` of type `object` supplied to " + - "`testComponent`, expected `mobx.ObservableMap`." - ) -}) - -test("observableArrayOf", () => { - typeCheckFail( - MRPropTypes.observableArrayOf(PropTypes.string), - 2, - "Invalid prop `testProp` of type `number` supplied to " + - "`testComponent`, expected `mobx.ObservableArray`." - ) - typeCheckFail( - MRPropTypes.observableArrayOf(PropTypes.string), - observable([2]), - "Invalid prop `testProp[0]` of type `number` supplied to " + - "`testComponent`, expected `string`." - ) - typeCheckFail( - MRPropTypes.observableArrayOf({ foo: (MRPropTypes as any).string } as any), - { foo: "bar" }, - "Property `testProp` of component `testComponent` has invalid PropType notation." - ) -}) - -test("arrayOrObservableArrayOf", () => { - typeCheckFail( - MRPropTypes.arrayOrObservableArrayOf(PropTypes.string), - 2, - "Invalid prop `testProp` of type `number` supplied to " + - "`testComponent`, expected `mobx.ObservableArray` or javascript `array`." - ) - typeCheckFail( - MRPropTypes.arrayOrObservableArrayOf(PropTypes.string), - observable([2]), - "Invalid prop `testProp[0]` of type `number` supplied to " + - "`testComponent`, expected `string`." - ) - typeCheckFail( - MRPropTypes.arrayOrObservableArrayOf(PropTypes.string), - [2], - "Invalid prop `testProp[0]` of type `number` supplied to " + - "`testComponent`, expected `string`." - ) - // TODO: - typeCheckFail( - MRPropTypes.arrayOrObservableArrayOf({ foo: (MRPropTypes as any).string } as any), - { foo: "bar" }, - "Property `testProp` of component `testComponent` has invalid PropType notation." - ) -}) diff --git a/packages/mobx-react/__tests__/utils/compile-ts.tsx b/packages/mobx-react/__tests__/utils/compile-ts.tsx index 022f599172..bd4f5c1386 100644 --- a/packages/mobx-react/__tests__/utils/compile-ts.tsx +++ b/packages/mobx-react/__tests__/utils/compile-ts.tsx @@ -1,15 +1,7 @@ import React from "react" import ReactDOM from "react-dom" import PropTypes from "prop-types" -import { - observer, - Provider, - inject, - Observer, - disposeOnUnmount, - PropTypes as MRPropTypes, - useLocalStore -} from "../src" +import { observer, Provider, inject, Observer, disposeOnUnmount, useLocalStore } from "../../src" @observer class T1 extends React.Component<{ pizza: number }, {}> { @@ -28,9 +20,6 @@ const T2 = observer( ) } - static propTypes = { - zoem: MRPropTypes.arrayOrObservableArray - } } ) From ea92a46e6da1b53eb6916ab65a8777fb26e05425 Mon Sep 17 00:00:00 2001 From: Venryx Date: Sun, 9 Mar 2025 16:07:30 -0700 Subject: [PATCH 3/4] * Added basic changelog entry for the removal of the PropTypes export. --- .changeset/fast-rocks-think.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fast-rocks-think.md diff --git a/.changeset/fast-rocks-think.md b/.changeset/fast-rocks-think.md new file mode 100644 index 0000000000..3fe6da170d --- /dev/null +++ b/.changeset/fast-rocks-think.md @@ -0,0 +1,5 @@ +--- +"mobx-react": major +--- + +Removed the deprecated PropTypes export, as React 19 no longer provides the types `React.Requireable` and `React.Validator`, and this was causing TypeScript errors with `@types/react@19.0.10`. From f5c0fe72b0617903032ee71fc7bfb5498de1a364 Mon Sep 17 00:00:00 2001 From: Venryx Date: Tue, 5 Aug 2025 02:51:32 -0700 Subject: [PATCH 4/4] * Updated compatibility matrix in mobx-react's readme, showing that version 10 adds (completed) support for React 19. --- packages/mobx-react/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/mobx-react/README.md b/packages/mobx-react/README.md index 4719876829..3ab0879c21 100644 --- a/packages/mobx-react/README.md +++ b/packages/mobx-react/README.md @@ -17,7 +17,8 @@ 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: | | ----------- | -------------------- | ------------------------ | -------------------------------------------------------------------------------- | -| v9 | 6.\* | >16.8 | Hooks, React 18.2 in strict mode | +| v10 | 6.\* | >19 | React 19 | +| v9 | 6.\* | >16.8 < 19 | 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 |