diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 4131373d..0f75f845 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -49,57 +49,6 @@ jobs: - run: npm run build --if-present - run: npm test - react-16-8: - runs-on: ubuntu-latest - - steps: - - run: echo "::add-mask::${{ secrets.STADIA_MAPS_API_KEY }}" - - uses: actions/checkout@v4 - - name: Use Node.js 18.x - uses: actions/setup-node@v4 - with: - node-version: 18.x - - run: | - npm i react@16.8 react-dom@16.8 react-test-renderer@16.8 @types/react@16 @types/react-dom@16 @testing-library/react@11 - - run: npm i - - run: tsc - - run: npm test - - run: npm run build:examples - - react-16-14: - runs-on: ubuntu-latest - - steps: - - run: echo "::add-mask::${{ secrets.STADIA_MAPS_API_KEY }}" - - uses: actions/checkout@v4 - - name: Use Node.js 18.x - uses: actions/setup-node@v4 - with: - node-version: 18.x - - run: | - npm i react@16.14 react-dom@16.14 react-test-renderer@16.14 @types/react@16 @types/react-dom@16 @testing-library/react@11 - - run: npm i - - run: tsc - - run: npm test - - run: npm run build:examples - - react-17: - runs-on: ubuntu-latest - - steps: - - run: echo "::add-mask::${{ secrets.STADIA_MAPS_API_KEY }}" - - uses: actions/checkout@v4 - - name: Use Node.js 18.x - uses: actions/setup-node@v4 - with: - node-version: 18.x - - run: | - npm i react@17 react-dom@17 react-test-renderer@17 @types/react@17 @types/react-dom@17 @testing-library/react@11 - - run: npm i - - run: tsc - - run: npm test - - run: npm run build:examples - react-18: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d6727dd..724f48db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### [3.0.1] 2024-05-23 + +- Drop React 16/17, React 18 is now required +- For OpenLayers 9.2.3 + # [3.0.0] 2024-05-22 - Do not support multiple OpenLayers versions, link each `rlayers` to one OpenLayers minor version diff --git a/README.md b/README.md index bf3d807e..fa4f6aff 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ _React_ is supported from version 16.8.0. | 2.1 (_obsolete_) | 6.10, 6.11, 6.12, 6.13, 6.14, 6.14.1, 6.15, 6.15.1, 7.0.0, 7.1.0, 7.2.0, 7.2.2, 7.3.0, 7.4.0, 7.5.1, 8.0.0, 8.1.0 | 16.8, 16.14, 17.0.2, 18.0.0, 18.1.0, 18.2.0 | | 2.2 (_obsolete_) | 6.10, 6.11, 6.12, 6.13, 6.14, 6.14.1, 6.15, 6.15.1, 7.0.0, 7.1.0, 7.2.0, 7.2.2, 7.3.0, 7.4.0, 7.5.1, 8.0.0, 8.1.0, 8.2.0 | 16.8, 16.14, 17.0.2, 18.0.0, 18.1.0, 18.2.0 | | 2.3 `(@latest)` | 6.10, 6.11, 6.12, 6.13, 6.14, 6.14.1, 6.15, 6.15.1, 7.0.0, 7.1.0, 7.2.0, 7.2.2, 7.3.0, 7.4.0, 7.5.1, 8.0.0, 8.1.0, 8.2.0, 9.0.0, 9.1.0 | 16.8, 16.14, 17.0.2, 18.0.0, 18.1.0, 18.2.0, 18.3.1 | -| 3.0 (`@next`) | 9.2.2 | 16.8, 16.14, 17.0.2, 18.0.0, 18.1.0, 18.2.0, 18.3.1 | +| 3.0 (`@next`) | 9.2.2 | 18.0.0, 18.1.0, 18.2.0, 18.3.1 | --- diff --git a/examples/index-react18.tsx b/examples/index-react18.tsx deleted file mode 100644 index 3ba52feb..00000000 --- a/examples/index-react18.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import ReactDOMClient from 'react-dom/client'; - -import debug from 'rlayers/debug'; - -import App from './App'; - -debug('React 18 mode'); -ReactDOMClient.createRoot(document.getElementById('root')).render(); diff --git a/examples/index.tsx b/examples/index.tsx index b443924c..e438453e 100644 --- a/examples/index.tsx +++ b/examples/index.tsx @@ -1,9 +1,8 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOMClient from 'react-dom/client'; import debug from 'rlayers/debug'; import App from './App'; -debug('React 16/17 mode'); -ReactDOM.render(, document.getElementById('root')); +ReactDOMClient.createRoot(document.getElementById('root')).render(); diff --git a/package.json b/package.json index cd053dbf..df0d86b6 100644 --- a/package.json +++ b/package.json @@ -94,8 +94,8 @@ }, "peerDependencies": { "ol": "9.2.2", - "react": ">=16.8", - "react-dom": ">=16.8" + "react": ">=18", + "react-dom": ">=18" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", @@ -109,6 +109,7 @@ "@types/proj4": "^2.5.2", "@types/react": "^18.3.2", "@types/react-dom": "^18.0.5", + "@types/semver": "^7.5.8", "@typescript-eslint/eslint-plugin": "^7.1.0", "@typescript-eslint/parser": "^7.1.0", "@typescript-eslint/typescript-estree": "^7.1.0", diff --git a/src/style/RStyle.tsx b/src/style/RStyle.tsx index 502ae47e..2d2c1312 100644 --- a/src/style/RStyle.tsx +++ b/src/style/RStyle.tsx @@ -1,5 +1,5 @@ import React, {PropsWithChildren} from 'react'; -import ReactDOM from 'react-dom'; +import {createRoot} from 'react-dom/client'; import {LRUCache} from 'lru-cache'; import {Map, Feature} from 'ol'; import Style, {StyleLike} from 'ol/style/Style'; @@ -71,12 +71,12 @@ export default class RStyle extends React.PureComponent {this.props.render(f, r)} ); - ReactDOM.render(render, document.createElement('div')); + createRoot(document.createElement('div')).render(reactElement); if (this.cache) this.cache.set(hash, style); return style; }; diff --git a/src/style/RStyleArray.tsx b/src/style/RStyleArray.tsx index f394303c..845c4d95 100644 --- a/src/style/RStyleArray.tsx +++ b/src/style/RStyleArray.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import {createRoot} from 'react-dom/client'; import {Feature} from 'ol'; import Style from 'ol/style/Style'; import Geometry from 'ol/geom/Geometry'; @@ -38,12 +38,12 @@ export default class RStyleArray extends RStyle { throw new TypeError('An RStyleArray should contain only RStyle elements'); }); const styleArray = []; - const render = ( + const reactElement = ( {element.props.children} ); - ReactDOM.render(render, document.createElement('div')); + createRoot(document.createElement('div')).render(reactElement); return styleArray as Style[]; } return this.ol as Style[]; diff --git a/test/RFeature.test.tsx b/test/RFeature.test.tsx index e2ce5c6f..53767b5f 100644 --- a/test/RFeature.test.tsx +++ b/test/RFeature.test.tsx @@ -1,12 +1,16 @@ -import * as fs from 'fs'; -import React from 'react'; +import React, {act as act_React19} from 'react'; import {fireEvent, render} from '@testing-library/react'; +import {act as act_React_18} from 'react-dom/test-utils'; +import semver from 'semver'; import {Polygon, Point} from 'ol/geom'; import {Feature} from 'ol'; import {RFeature, RLayerVector, RMap, RContext, ROverlay} from 'rlayers'; import * as common from './common'; -import {act} from 'react-dom/test-utils'; + +const act: (callback: () => T | Promise) => Promise = semver.gte(React.version, '18.3.0') + ? act_React19 + : act_React_18; describe('', () => { it('should create features', async () => { diff --git a/test/RStyle.test.tsx b/test/RStyle.test.tsx index ae06f500..ee97bf72 100644 --- a/test/RStyle.test.tsx +++ b/test/RStyle.test.tsx @@ -1,5 +1,7 @@ -import React from 'react'; +import React, {act as act_React19} from 'react'; import {fireEvent, render} from '@testing-library/react'; +import {act as act_React_18} from 'react-dom/test-utils'; +import semver from 'semver'; import {Feature} from 'ol'; import {Style, Circle, Image, RegularShape} from 'ol/style'; @@ -20,6 +22,10 @@ import { import {Point} from 'ol/geom'; import * as common from './common'; +const act: (callback: () => T | Promise) => Promise = semver.gte(React.version, '18.3.0') + ? act_React19 + : act_React_18; + describe('', () => { it('should create a basic icon style', async () => { const ref = createRStyle(); @@ -148,7 +154,9 @@ describe('', () => { geometry: new Point(common.coords.ArcDeTriomphe), name: 'text' }); - const style = (RStyle.getStyle(ref) as (Feature, number) => Style)(f, 100); + const style = await act(() => { + return (RStyle.getStyle(ref) as (Feature, number) => Style)(f, 100); + }); expect(style.getText()?.getText()).toBe('text'); expect(style.getText()?.getFont()).toBe('bold 25px sans-serif'); expect(style.getText()?.getStroke()?.getWidth()).toBe(100); @@ -229,7 +237,9 @@ describe('', () => { geometry: new Point(common.coords.ArcDeTriomphe), name: 'text14' }); - const style = (RStyle.getStyle(ref) as (Feature) => Style)(f); + const style = await act(() => { + return (RStyle.getStyle(ref) as (Feature) => Style)(f); + }); expect(style.getText()?.getText()).toBe('text14'); expect(style.getText()?.getStroke()?.getWidth()).toBe(14); expect(ref.current?.cache.get(f.get('name'))).toBe(style); @@ -267,7 +277,9 @@ describe('', () => { geometry: new Point(common.coords.ArcDeTriomphe), name: 'text9' }); - const style = (ref.current?.ol.getStyle() as (Feature) => Style)(f); + const style = await act(() => { + return (ref.current?.ol.getStyle() as (Feature) => Style)(f); + }); expect(style.getText()?.getText()).toBe('text9'); expect(style.getText()?.getStroke()?.getWidth()).toBe(9); }); @@ -420,12 +432,19 @@ describe('', () => { geometry: new Point(common.coords.ArcDeTriomphe), name: 'text1' }); - expect(((ref.current as RStyleArray).style(f, 0) as Style[]).length).toBe(2); - let style = (RStyle.getStyle(ref) as (Feature) => Style[])(f); + const styleArray = await act(() => { + return (ref.current as RStyleArray).style(f, 0) as Style[]; + }); + expect(styleArray.length).toBe(2); + let style = await act(() => { + return (RStyle.getStyle(ref) as (Feature) => Style[])(f); + }); expect(style[0].getText()?.getText()).toBe('text1'); expect(style[1].getStroke()?.getWidth()).toBe(3); f.set('name', 'text2'); - style = (RStyle.getStyle(ref) as (Feature) => Style[])(f); + style = await act(() => { + return (RStyle.getStyle(ref) as (Feature) => Style[])(f); + }); expect(style[0].getText()?.getText()).toBe('text2'); rerender( ', () => { /> ); f.set('name', 'text3'); - style = (RStyle.getStyle(ref) as (Feature) => Style[])(f); + style = await act(() => { + return (RStyle.getStyle(ref) as (Feature) => Style[])(f); + }); expect(style[1].getText()?.getText()).toBe('text3'); unmount(); }); diff --git a/webpack.config.ts b/webpack.config.ts index 3c2f0c96..a314e801 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -5,18 +5,10 @@ import 'webpack-dev-server'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; import {TsconfigPathsPlugin} from 'tsconfig-paths-webpack-plugin'; -import React from 'react'; const webpackConfig = (env): webpack.Configuration => { - let reactMajorVersion = +React.version.split('.')[0]; - if (reactMajorVersion >= 18) { - console.log('React 18 detected'); - } else { - console.log('React 16/17 detected'); - } - const conf: webpack.Configuration = { - entry: reactMajorVersion >= 18 ? './examples/index-react18.tsx' : './examples/index.tsx', + entry: './examples/index.tsx', ...(env.production || !env.development ? {} : {devtool: 'eval-source-map'}), resolve: { alias: { @@ -83,15 +75,6 @@ const webpackConfig = (env): webpack.Configuration => { } }; - if (reactMajorVersion < 18) { - // This is needed for React 16/17 as otherwise ts-loader - // will pick `index-react18.tsx` and will fail transpiling it - conf.module!.rules!.unshift({ - test: /index-react18\.tsx?$/, - loader: 'null-loader' - }); - } - if (!env.development) { conf.plugins!.push(new ForkTsCheckerWebpackPlugin()); }