From e3ba8c2918817659a5319d22e0a9ec82d519d7ee Mon Sep 17 00:00:00 2001 From: Naden Date: Sun, 13 Aug 2023 05:10:30 +1000 Subject: [PATCH] Add support for React 18 (#665) Co-authored-by: Naden --- docs/public/docs/installation.md | 12 +++++------ history/build.sbt | 2 +- package.json | 2 +- project/plugins.sbt | 2 +- scalajsReactInterop/build.sbt | 4 ++++ scalajsReactInterop/package.json | 5 +++-- tests/build.sbt | 4 ++++ tests/package.json | 5 +++-- .../test/scala/slinky/web/ReactDOMTest.scala | 21 +++++++++++++++++++ web/build.sbt | 2 +- web/src/main/scala/slinky/web/ReactDOM.scala | 14 +++++++++++++ 11 files changed, 59 insertions(+), 14 deletions(-) diff --git a/docs/public/docs/installation.md b/docs/public/docs/installation.md index 6ea6fe4f..f7a7d106 100644 --- a/docs/public/docs/installation.md +++ b/docs/public/docs/installation.md @@ -37,8 +37,8 @@ For example, If you're using [scalajs-bundler](https://scalacenter.github.io/sca ``` enablePlugins(ScalaJSBundlerPlugin) // at the top of the file -npmDependencies in Compile += "react" -> "16.12.0" -npmDependencies in Compile += "react-dom" -> "16.12.0" +npmDependencies in Compile += "react" -> "18.2.0" +npmDependencies in Compile += "react-dom" -> "18.2.0" ``` If you are using `jsDependencies` you should add, instead: @@ -47,13 +47,13 @@ enablePlugins(ScalaJSPlugin) // at the top of the file // React itself (note the filenames, adjust as needed to remove addons) jsDependencies ++= Seq( - "org.webjars.npm" % "react" % "16.12.0" % Test / "umd/react.development.js" + "org.webjars.npm" % "react" % "18.2.0" % Test / "umd/react.development.js" minified "umd/react.production.min.js" commonJSName "React", - "org.webjars.npm" % "react-dom" % "16.12.0" % Test / "umd/react-dom.development.js" + "org.webjars.npm" % "react-dom" % "18.2.0" % Test / "umd/react-dom.development.js" minified "umd/react-dom.production.min.js" dependsOn "umd/react.development.js" commonJSName "ReactDOM", - "org.webjars.npm" % "react-dom" % "16.12.0" % Test / "umd/react-dom-test-utils.development.js" + "org.webjars.npm" % "react-dom" % "18.2.0" % Test / "umd/react-dom-test-utils.development.js" minified "umd/react-dom-test-utils.production.min.js" dependsOn "umd/react-dom.development.js" commonJSName "ReactTestUtils", - "org.webjars.npm" % "react-dom" % "16.12.0" % Test / "umd/react-dom-server.browser.development.js" + "org.webjars.npm" % "react-dom" % "18.2.0" % Test / "umd/react-dom-server.browser.development.js" minified "umd/react-dom-server.browser.production.min.js" dependsOn "umd/react-dom.development.js" commonJSName "ReactDOMServer" ) ``` diff --git a/history/build.sbt b/history/build.sbt index 44899fc6..bd15a59b 100644 --- a/history/build.sbt +++ b/history/build.sbt @@ -2,4 +2,4 @@ enablePlugins(ScalaJSPlugin) name := "slinky-history" -libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.4.0" +libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.6.0" diff --git a/package.json b/package.json index 9a56c43d..bc1e278e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, "dependencies": { - "jsdom": "^9.12.0" + "jsdom": "^22.1.0" } } diff --git a/project/plugins.sbt b/project/plugins.sbt index 1c976265..1ab6eb06 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -10,7 +10,7 @@ addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion) libraryDependencies ++= { if (scalaJSVersion.startsWith("0.6.")) Nil - else Seq("org.scala-js" %% "scalajs-env-jsdom-nodejs" % "1.0.0") + else Seq("org.scala-js" %% "scalajs-env-jsdom-nodejs" % "1.1.0") } libraryDependencies ++= { diff --git a/scalajsReactInterop/build.sbt b/scalajsReactInterop/build.sbt index c01412b8..75b9012d 100644 --- a/scalajsReactInterop/build.sbt +++ b/scalajsReactInterop/build.sbt @@ -23,8 +23,12 @@ Test / jsEnv := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv() Test / unmanagedResourceDirectories += baseDirectory.value / "node_modules" jsDependencies ++= Seq( + ((ProvidedJS / "text-enc/lib/encoding.js") + .minified("text-enc/lib/encoding.js") + .commonJSName("TextEnc")) % Test, ((ProvidedJS / "react/umd/react.development.js") .minified("react/umd/react.production.min.js") + .dependsOn("text-enc/lib/encoding.js") .commonJSName("React")) % Test, ((ProvidedJS / "react-dom/umd/react-dom.development.js") .minified("react-dom/umd/react-dom.production.min.js") diff --git a/scalajsReactInterop/package.json b/scalajsReactInterop/package.json index c1551cc7..7ff8397a 100644 --- a/scalajsReactInterop/package.json +++ b/scalajsReactInterop/package.json @@ -1,7 +1,8 @@ { "private": true, "dependencies": { - "react": "16.12.0", - "react-dom": "16.12.0" + "react": "18.2.0", + "react-dom": "18.2.0", + "text-enc": "0.7.0" } } diff --git a/tests/build.sbt b/tests/build.sbt index b77eee09..64c8f5be 100644 --- a/tests/build.sbt +++ b/tests/build.sbt @@ -20,7 +20,11 @@ Test / scalaJSLinkerConfig ~= { Test / unmanagedResourceDirectories += baseDirectory.value / "node_modules" jsDependencies ++= Seq( + ((ProvidedJS / "text-enc/lib/encoding.js") + .minified("text-enc/lib/encoding.js") + .commonJSName("TextEnc")) % Test, ((ProvidedJS / "react/umd/react.development.js") + .dependsOn("text-enc/lib/encoding.js") .minified("react/umd/react.production.min.js") .commonJSName("React")) % Test, ((ProvidedJS / "react-dom/umd/react-dom.development.js") diff --git a/tests/package.json b/tests/package.json index c1551cc7..7ff8397a 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,8 @@ { "private": true, "dependencies": { - "react": "16.12.0", - "react-dom": "16.12.0" + "react": "18.2.0", + "react-dom": "18.2.0", + "text-enc": "0.7.0" } } diff --git a/tests/src/test/scala/slinky/web/ReactDOMTest.scala b/tests/src/test/scala/slinky/web/ReactDOMTest.scala index fc25ddf8..316969e1 100644 --- a/tests/src/test/scala/slinky/web/ReactDOMTest.scala +++ b/tests/src/test/scala/slinky/web/ReactDOMTest.scala @@ -2,6 +2,7 @@ package slinky.web import slinky.core.ComponentWrapper import slinky.core.facade.ReactElement +import slinky.web.ReactDOMClient.createRoot import org.scalajs.dom.{Element, document} import scala.scalajs.js @@ -23,6 +24,13 @@ object TestComponent extends ComponentWrapper { } class ReactDOMTest extends AnyFunSuite { + test("Renders a single element into the DOM using createRoot") { + val target = document.createElement("div") + ReactDOM.flushSync(() => createRoot(target).render(a())) + + assert(target.innerHTML == "") + } + test("Renders a single element into the DOM") { val target = document.createElement("div") ReactDOM.render( @@ -58,6 +66,19 @@ class ReactDOMTest extends AnyFunSuite { assert(target.innerHTML == "
") } + test("unmount clears out the container") { + val container = document.createElement("div") + val root = createRoot(container) + + ReactDOM.flushSync(() => root.render(div("hello"))) + + assert(container.innerHTML == "
hello
") + + root.unmount() + + assert(container.innerHTML.length == 0) + } + test("unmountComponentAtNode clears out the container") { val container = document.createElement("div") ReactDOM.render( diff --git a/web/build.sbt b/web/build.sbt index df0edb67..0d6aeaff 100644 --- a/web/build.sbt +++ b/web/build.sbt @@ -2,7 +2,7 @@ enablePlugins(ScalaJSPlugin) name := "slinky-web" -libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.4.0" +libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.6.0" tpolecatDevModeOptions ~= { opts => opts.filterNot( diff --git a/web/src/main/scala/slinky/web/ReactDOM.scala b/web/src/main/scala/slinky/web/ReactDOM.scala index 8bfe2069..25f54626 100644 --- a/web/src/main/scala/slinky/web/ReactDOM.scala +++ b/web/src/main/scala/slinky/web/ReactDOM.scala @@ -13,6 +13,8 @@ object ReactDOM extends js.Object { def hydrate(component: ReactElement, target: Element): ReactInstance = js.native def findDOMNode(instance: React.Component): Element = js.native + def flushSync[T](callback: js.Function0[T]): T = js.native + def unmountComponentAtNode(container: Element): Unit = js.native /** @@ -37,3 +39,15 @@ object ReactDOMServer extends js.Object { def renderToNodeStream(element: ReactElement): js.Object = js.native def renderToStaticNodeStream(element: ReactElement): js.Object = js.native } + +trait ReactRoot extends js.Object { + def render(component: ReactElement): ReactInstance + def unmount(): Unit +} + +@js.native +@JSImport("react-dom/client", JSImport.Namespace, "ReactDOM") +object ReactDOMClient extends js.Object { + def createRoot(target: Element): ReactRoot = js.native + def hydrate(component: ReactElement, target: Element): ReactRoot = js.native +}