Skip to content

Commit

Permalink
React 19 support (#3985)
Browse files Browse the repository at this point in the history
* deprecate `IObserver` options and its properties in JSDoc

* Preparing for React 19 - some tests fail

* RTL hooks error

* skipping legacy context tests

* skipping propTypes and defaultProps forwarding tests

* skipping class propTypes test

* render time check

* changed some assertions

* added extra render

* added todo

* added warning for legacy context types in function components

* added changeset

---------

Co-authored-by: Michel Weststrate <[email protected]>
  • Loading branch information
imjordanxd and mweststrate authored Dec 11, 2024
1 parent 9944edc commit 2587df3
Show file tree
Hide file tree
Showing 16 changed files with 316 additions and 203 deletions.
6 changes: 6 additions & 0 deletions .changeset/unlucky-readers-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"mobx-react-lite": minor
"mobx-react": minor
---

* Added React 19 support, fixes #3986
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@
"@changesets/cli": "^2.11.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^16.0.1",
"@testing-library/react-hooks": "7.0.2",
"@testing-library/react": "^16.1.0",
"@types/jest": "^26.0.15",
"@types/node": "18",
"@types/prop-types": "^15.5.2",
Expand All @@ -62,8 +61,8 @@
"prettier": "^2.8.4",
"pretty-quick": "3.1.0",
"prop-types": "15.6.2",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-test-renderer": "^18.0.0",
"serializr": "^2.0.3",
"tape": "^5.0.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/mobx-react-lite/__tests__/observer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function runTestSuite(mode: "observer" | "useObserver") {
list: 0
}

const TodoItem = obsComponent(({ todo }: { todo: typeof store.todos[0] }) => {
const TodoItem = obsComponent(({ todo }: { todo: (typeof store.todos)[0] }) => {
renderings.item++
return <li>|{todo.title}</li>
}, true)
Expand Down Expand Up @@ -997,7 +997,7 @@ it("dependencies should not become temporarily unobserved", async () => {
expect(doubleDisposed).toBeCalledTimes(1)
})

it("Legacy context support", () => {
it.skip("Legacy context support", () => {
const contextKey = "key"
const contextValue = "value"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { act, cleanup, render } from "@testing-library/react"
import { renderHook } from "@testing-library/react-hooks"
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"
Expand Down Expand Up @@ -81,7 +80,7 @@ describe("base useAsObservableSource should work", () => {
})
expect(container.querySelector("span")!.innerHTML).toBe("22")
expect(counterRender).toBe(2)
expect(observerRender).toBe(3)
expect(observerRender).toBe(4)
expect(consoleWarnMock).toMatchSnapshot()
})

Expand Down Expand Up @@ -288,29 +287,62 @@ describe("combining observer with props and stores", () => {
describe("enforcing actions", () => {
it("'never' should work", () => {
configure({ enforceActions: "never" })
const { result } = renderHook(() => {
const [thing, setThing] = React.useState("world")
useAsObservableSource({ hello: thing })
useEffect(() => setThing("react"), [])
})
expect(result.error).not.toBeDefined()
const onError = jest.fn()
renderHook(
() => {
const [thing, setThing] = React.useState("world")
useAsObservableSource({ hello: thing })
useEffect(() => setThing("react"), [])
},
{
wrapper: class extends React.Component<React.PropsWithChildren> {
componentDidCatch = onError
render() {
return this.props.children
}
}
}
)
expect(onError).not.toBeCalled()
})
it("only when 'observed' should work", () => {
configure({ enforceActions: "observed" })
const { result } = renderHook(() => {
const [thing, setThing] = React.useState("world")
useAsObservableSource({ hello: thing })
useEffect(() => setThing("react"), [])
})
expect(result.error).not.toBeDefined()
const onError = jest.fn()
renderHook(
() => {
const [thing, setThing] = React.useState("world")
useAsObservableSource({ hello: thing })
useEffect(() => setThing("react"), [])
},
{
wrapper: class extends React.Component<React.PropsWithChildren> {
componentDidCatch = onError
render() {
return this.props.children
}
}
}
)
expect(onError).not.toBeCalled()
})
it("'always' should work", () => {
configure({ enforceActions: "always" })
const { result } = renderHook(() => {
const [thing, setThing] = React.useState("world")
useAsObservableSource({ hello: thing })
useEffect(() => setThing("react"), [])
})
expect(result.error).not.toBeDefined()
const onError = jest.fn()
renderHook(
() => {
const [thing, setThing] = React.useState("world")
useAsObservableSource({ hello: thing })
useEffect(() => setThing("react"), [])
},
{
wrapper: class extends React.Component<React.PropsWithChildren> {
componentDidCatch = onError
render() {
return this.props.children
}
}
}
)
expect(onError).not.toBeCalled()
})
})
72 changes: 52 additions & 20 deletions packages/mobx-react-lite/__tests__/useAsObservableSource.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { act, cleanup, render } from "@testing-library/react"
import { renderHook } from "@testing-library/react-hooks"
import { act, cleanup, render, renderHook } from "@testing-library/react"
import mockConsole from "jest-mock-console"
import { autorun, configure, observable } from "mobx"
import * as React from "react"
Expand Down Expand Up @@ -351,30 +350,63 @@ describe("combining observer with props and stores", () => {
describe("enforcing actions", () => {
it("'never' should work", () => {
configure({ enforceActions: "never" })
const { result } = renderHook(() => {
const [thing, setThing] = React.useState("world")
useLocalObservable(() => ({ hello: thing }))
useEffect(() => setThing("react"), [])
})
expect(result.error).not.toBeDefined()
const onError = jest.fn()
renderHook(
() => {
const [thing, setThing] = React.useState("world")
useLocalObservable(() => ({ hello: thing }))
useEffect(() => setThing("react"), [])
},
{
wrapper: class extends React.Component<React.PropsWithChildren> {
componentDidCatch = onError
render() {
return this.props.children
}
}
}
)
expect(onError).not.toBeCalled()
})
it("only when 'observed' should work", () => {
configure({ enforceActions: "observed" })
const { result } = renderHook(() => {
const [thing, setThing] = React.useState("world")
useLocalObservable(() => ({ hello: thing }))
useEffect(() => setThing("react"), [])
})
expect(result.error).not.toBeDefined()
const onError = jest.fn()
renderHook(
() => {
const [thing, setThing] = React.useState("world")
useLocalObservable(() => ({ hello: thing }))
useEffect(() => setThing("react"), [])
},
{
wrapper: class extends React.Component<React.PropsWithChildren> {
componentDidCatch = onError
render() {
return this.props.children
}
}
}
)
expect(onError).not.toBeCalled()
})
it("'always' should work", () => {
configure({ enforceActions: "always" })
const { result } = renderHook(() => {
const [thing, setThing] = React.useState("world")
useLocalObservable(() => ({ hello: thing }))
useEffect(() => setThing("react"), [])
})
expect(result.error).not.toBeDefined()
const onError = jest.fn()
renderHook(
() => {
const [thing, setThing] = React.useState("world")
useLocalObservable(() => ({ hello: thing }))
useEffect(() => setThing("react"), [])
},
{
wrapper: class extends React.Component<React.PropsWithChildren> {
componentDidCatch = onError
render() {
return this.props.children
}
}
}
)
expect(onError).not.toBeCalled()
})
})

Expand Down
Loading

0 comments on commit 2587df3

Please sign in to comment.