Skip to content

Commit 6e0f41c

Browse files
committed
Require React 18
1 parent 20e4455 commit 6e0f41c

File tree

14 files changed

+56
-123
lines changed

14 files changed

+56
-123
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ After a release, please make sure to run `bundle exec rake update_changelog`. Th
2323

2424
Changes since the last non-beta release.
2525

26+
#### Removed
27+
28+
- Support for React 16 and 17. [PR 1710](https://github.com/shakacode/react_on_rails/pull/1710) by [alexeyr-ci](https://github.com/alexeyr-ci).
29+
30+
#### Breaking Changes
31+
32+
- React >=18 is now required.
33+
2634
### [15.0.0] - 2025-08-28
2735

2836
See [Release Notes](docs/release-notes/15.0.0.md) for full details.

docs/release-notes/16.0.0.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# React on Rails 16.0.0 Release Notes
2+
3+
Also see the Changelog for 16.0.0 (TODO: insert link once released).
4+
5+
## Breaking Changes
6+
7+
- Support for React 16 and 17 is dropped.

node_package/src/ClientSideRenderer.ts

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/* eslint-disable max-classes-per-file */
22

33
import type { ReactElement } from 'react';
4-
import type { RailsContext, RegisteredComponent, RenderFunction, Root } from './types/index.ts';
4+
import type { Root } from 'react-dom/client';
5+
import type { RailsContext, RegisteredComponent, RenderFunction } from './types/index.ts';
56

67
import { getRailsContext, resetRailsContext } from './context.ts';
78
import createReactOutput from './createReactOutput.ts';
89
import { isServerRenderHash } from './isServerRenderResult.ts';
9-
import { supportsHydrate, supportsRootApi, unmountComponentAtNode } from './reactApis.cts';
1010
import reactHydrateOrRender from './reactHydrateOrRender.ts';
1111
import { debugTurbolinks } from './turbolinksUtils.ts';
1212
import * as StoreRegistry from './StoreRegistry.ts';
@@ -100,8 +100,8 @@ class ComponentRenderer {
100100
return;
101101
}
102102

103-
// Hydrate if available and was server rendered
104-
const shouldHydrate = supportsHydrate && !!domNode.innerHTML;
103+
// Hydrate if the node was server rendered
104+
const shouldHydrate = !!domNode.innerHTML;
105105

106106
const reactElementOrRouterResult = createReactOutput({
107107
componentObj,
@@ -117,15 +117,12 @@ class ComponentRenderer {
117117
You returned a server side type of react-router error: ${JSON.stringify(reactElementOrRouterResult)}
118118
You should return a React.Component always for the client side entry point.`);
119119
} else {
120-
const rootOrElement = reactHydrateOrRender(
120+
this.root = reactHydrateOrRender(
121121
domNode,
122122
reactElementOrRouterResult as ReactElement,
123123
shouldHydrate,
124124
);
125125
this.state = 'rendered';
126-
if (supportsRootApi) {
127-
this.root = rootOrElement as Root;
128-
}
129126
}
130127
}
131128
} catch (e: unknown) {
@@ -143,27 +140,8 @@ You should return a React.Component always for the client side entry point.`);
143140
}
144141
this.state = 'unmounted';
145142

146-
if (supportsRootApi) {
147-
this.root?.unmount();
148-
this.root = undefined;
149-
} else {
150-
const domNode = document.getElementById(this.domNodeId);
151-
if (!domNode) {
152-
return;
153-
}
154-
155-
try {
156-
// eslint-disable-next-line @typescript-eslint/no-deprecated
157-
unmountComponentAtNode(domNode);
158-
} catch (e: unknown) {
159-
const error = e instanceof Error ? e : new Error('Unknown error');
160-
console.info(
161-
`Caught error calling unmountComponentAtNode: ${error.message} for domNode`,
162-
domNode,
163-
error,
164-
);
165-
}
166-
}
143+
this.root?.unmount();
144+
this.root = undefined;
167145
}
168146

169147
waitUntilRendered(): Promise<void> {

node_package/src/ReactDOMServer.cts

Lines changed: 0 additions & 4 deletions
This file was deleted.

node_package/src/ReactOnRails.client.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ReactElement } from 'react';
2+
import type { Root } from 'react-dom/client';
23
import * as ClientStartup from './clientStartup.ts';
34
import { renderOrHydrateComponent, hydrateStore } from './ClientSideRenderer.ts';
45
import * as ComponentRegistry from './ComponentRegistry.ts';
@@ -9,7 +10,6 @@ import * as Authenticity from './Authenticity.ts';
910
import type {
1011
RegisteredComponent,
1112
RenderResult,
12-
RenderReturnType,
1313
ReactComponentOrRenderFunction,
1414
AuthenticityHeaders,
1515
Store,
@@ -64,7 +64,7 @@ globalThis.ReactOnRails = {
6464
return StoreRegistry.getOrWaitForStoreGenerator(name);
6565
},
6666

67-
reactHydrateOrRender(domNode: Element, reactElement: ReactElement, hydrate: boolean): RenderReturnType {
67+
reactHydrateOrRender(domNode: Element, reactElement: ReactElement, hydrate: boolean): Root {
6868
return reactHydrateOrRender(domNode, reactElement, hydrate);
6969
},
7070

@@ -128,7 +128,7 @@ globalThis.ReactOnRails = {
128128
StoreRegistry.clearHydratedStores();
129129
},
130130

131-
render(name: string, props: Record<string, string>, domNodeId: string, hydrate: boolean): RenderReturnType {
131+
render(name: string, props: Record<string, string>, domNodeId: string, hydrate: boolean): Root {
132132
const componentObj = ComponentRegistry.get(name);
133133
const reactElement = createReactOutput({ componentObj, props, domNodeId });
134134

node_package/src/handleError.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { renderToString } from './ReactDOMServer.cts';
2+
import { renderToString } from 'react-dom/server';
33
import type { ErrorOptions } from './types/index.ts';
44

55
function handleRenderFunctionIssue(options: ErrorOptions): string {

node_package/src/reactApis.cts

Lines changed: 0 additions & 61 deletions
This file was deleted.

node_package/src/reactApis.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as React from 'react';
2+
3+
// eslint-disable-next-line import/prefer-default-export
4+
export const ensureReactUseAvailable = () => {
5+
if (!('use' in React) || typeof React.use !== 'function') {
6+
throw new Error(
7+
'React.use is not defined. Please ensure you are using React 19 to use server components.',
8+
);
9+
}
10+
};
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import type { ReactElement } from 'react';
2-
import type { RenderReturnType } from './types/index.ts';
3-
import { reactHydrate, reactRender } from './reactApis.cts';
2+
import { createRoot, hydrateRoot, Root } from 'react-dom/client';
43

54
export default function reactHydrateOrRender(
65
domNode: Element,
76
reactElement: ReactElement,
87
hydrate: boolean,
9-
): RenderReturnType {
10-
return hydrate ? reactHydrate(domNode, reactElement) : reactRender(domNode, reactElement);
8+
): Root {
9+
if (hydrate) {
10+
return hydrateRoot(domNode, reactElement);
11+
}
12+
13+
const root = createRoot(domNode);
14+
root.render(reactElement);
15+
return root;
1116
}

node_package/src/serverRenderReactComponent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import * as React from 'react';
22
import type { ReactElement } from 'react';
3+
import { renderToString } from 'react-dom/server';
34

45
import * as ComponentRegistry from './ComponentRegistry.ts';
56
import createReactOutput from './createReactOutput.ts';
67
import { isPromise, isServerRenderHash } from './isServerRenderResult.ts';
78
import buildConsoleReplay from './buildConsoleReplay.ts';
89
import handleError from './handleError.ts';
9-
import { renderToString } from './ReactDOMServer.cts';
1010
import { createResultObject, convertToError, validateComponent } from './serverRenderUtils.ts';
1111
import type {
1212
CreateReactOutputResult,

0 commit comments

Comments
 (0)