Skip to content

Commit 6e443c0

Browse files
committed
feat(*): react 19 support update
1 parent 25b4b60 commit 6e443c0

8 files changed

Lines changed: 169 additions & 90 deletions

File tree

global.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import type * as React from 'react';
2+
import type * as ReactDOM from 'react-dom';
3+
import type * as JSXRuntime from 'react/jsx-runtime';
14
declare global {
25
interface Window {
36
SP_REACT: typeof React;
7+
SP_REACTDOM: typeof ReactDOM;
8+
SP_JSX: typeof JSXRuntime;
49
}
510
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@
4747
"@semantic-release/changelog": "^6.0.3",
4848
"@semantic-release/git": "^10.0.1",
4949
"@types/jest": "^29.5.14",
50-
"@types/react": "18.3.3",
51-
"@types/react-dom": "18.3.0",
50+
"@types/react": "19.1.1",
51+
"@types/react-dom": "19.1.1",
5252
"@types/react-router": "5.1.20",
5353
"commitizen": "^4.3.1",
5454
"husky": "^9.1.7",

pnpm-lock.yaml

Lines changed: 14 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Focusable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ export interface FocusableProps extends HTMLAttributes<HTMLDivElement>, FooterLe
1717
const focusableRegex = createPropListRegex(["flow-children", "onActivate", "onCancel", "focusClassName", "focusWithinClassName"]);
1818

1919
export const Focusable = findModuleExport((e: Export) =>
20-
e?.render?.toString && focusableRegex.test(e.render.toString())
20+
(typeof e == 'function' && e?.toString && focusableRegex.test(e.toString())) || (e?.render?.toString && focusableRegex.test(e.render.toString()))
2121
) as FC<FocusableProps & RefAttributes<HTMLDivElement>>;

src/custom-components/ReorderableList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export type ReorderableListEntryProps<T> = {
108108
reorderEntryFunc: CallableFunction;
109109
reorderEnabled: boolean;
110110
animate: boolean;
111-
children: ReactElement | null;
111+
children: ReactElement<any> | null;
112112
};
113113

114114
function ReorderableItem<T>(props: ReorderableListEntryProps<T>) {

src/utils/react/fc.ts

Lines changed: 134 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -69,71 +69,145 @@ export function injectFCTrampoline(component: FC, customHooks?: any): FCTrampoli
6969

7070
let renderHookStep = 0;
7171

72-
// Accessed two times, once directly before class instantiation, and again in some extra logic we don't need to worry about that we hanlde below just in case.
73-
Object.defineProperty(component, "contextType", {
74-
configurable: true,
75-
get: function () {
76-
loggingEnabled && logger.debug("get contexttype", this, stubsApplied, renderHookStep);
77-
loggingEnabled && console.trace("contextType trace");
78-
if (renderHookStep == 0) renderHookStep = 1;
79-
else if (renderHookStep == 3) renderHookStep = 4;
80-
return this._contextType;
81-
},
82-
set: function (value) {
83-
this._contextType = value;
84-
}
85-
});
72+
if (window.SP_REACTDOM.version.startsWith("19.")) {
73+
// Accessed two times directly before class instantiation on path A and once on path B
74+
Object.defineProperty(component, "contextType", {
75+
configurable: true,
76+
get: function () {
77+
loggingEnabled && logger.debug("get contexttype", this, this._contextType, stubsApplied, renderHookStep);
78+
loggingEnabled && console.trace("contextType trace");
79+
if (renderHookStep == 0) {
80+
renderHookStep = 1;
81+
}
82+
if (this._contextType == null) {
83+
this._contextType = {};
84+
}
85+
if (!this._contextType.appliedCurrentValueHook) {
86+
logger.debug("applied currentvalue hook");
87+
this._contextType.appliedCurrentValueHook = true;
88+
Object.defineProperty(this._contextType, "_currentValue", {
89+
configurable: true,
90+
get: function () {
91+
loggingEnabled && logger.debug("get currentValue", this, stubsApplied, renderHookStep);
92+
loggingEnabled && console.trace("currentValue trace");
93+
if (renderHookStep == 1) {
94+
renderHookStep = 2;
95+
applyStubsIfNeeded();
96+
}
97+
return this.__currentValue;
98+
},
99+
set: function (value) {
100+
return this.__currentValue = value;
101+
}
102+
});
103+
}
104+
return this._contextType;
105+
},
106+
set: function (value) {
107+
this._contextType = value;
108+
}
109+
});
86110

87-
// Always accessed directly after contextType for the path we want to catch.
88-
Object.defineProperty(component, "contextTypes", {
89-
configurable: true,
90-
get: function () {
91-
loggingEnabled && logger.debug("get contexttypes", this, stubsApplied, renderHookStep);
92-
loggingEnabled && console.trace("contextTypes trace");
93-
if (renderHookStep == 1) {
94-
renderHookStep = 2;
95-
applyStubsIfNeeded();
96-
};
97-
return this._contextTypes;
98-
},
99-
set: function (value) {
100-
this._contextTypes = value;
101-
}
102-
});
111+
// Set directly after class is instantiated
112+
Object.defineProperty(component.prototype, "updater", {
113+
configurable: true,
114+
get: function () {
115+
return this._updater;
116+
},
117+
set: function (value) {
118+
loggingEnabled && logger.debug("set updater", this, value, stubsApplied, renderHookStep);
119+
loggingEnabled && console.trace("updater trace");
120+
if (renderHookStep == 1 || renderHookStep == 2) {
121+
renderHookStep = 0;
122+
removeStubsIfNeeded();
123+
}
124+
return this._updater = value;
125+
}
126+
});
103127

104-
// Set directly after class is instantiated
105-
Object.defineProperty(component.prototype, "updater", {
106-
configurable: true,
107-
get: function () {
108-
return this._updater;
109-
},
110-
set: function (value) {
111-
loggingEnabled && logger.debug("set updater", this, value, stubsApplied, renderHookStep);
112-
loggingEnabled && console.trace("updater trace");
113-
if (renderHookStep == 2) {
114-
renderHookStep = 0;
115-
removeStubsIfNeeded();
128+
// Prevents the second contextType access from leaving its hooks around
129+
Object.defineProperty(component, "getDerivedStateFromProps", {
130+
configurable: true,
131+
get: function () {
132+
loggingEnabled && logger.debug("get getDerivedStateFromProps", this, stubsApplied, renderHookStep);
133+
loggingEnabled && console.trace("getDerivedStateFromProps trace");
134+
if (renderHookStep == 1|| renderHookStep == 2) {
135+
renderHookStep = 0;
136+
removeStubsIfNeeded();
137+
}
138+
return this._getDerivedStateFromProps;
139+
},
140+
set: function (value) {
141+
this._getDerivedStateFromProps = value;
116142
}
117-
return this._updater = value;
118-
}
119-
});
143+
});
144+
} else if (window.SP_REACTDOM.version.startsWith("18.")) {
145+
// Accessed two times, once directly before class instantiation, and again in some extra logic we don't need to worry about that we hanlde below just in case.
146+
Object.defineProperty(component, "contextType", {
147+
configurable: true,
148+
get: function () {
149+
loggingEnabled && logger.debug("get contexttype", this, stubsApplied, renderHookStep);
150+
loggingEnabled && console.trace("contextType trace");
151+
if (renderHookStep == 0) renderHookStep = 1;
152+
else if (renderHookStep == 3) renderHookStep = 4;
153+
return this._contextType;
154+
},
155+
set: function (value) {
156+
this._contextType = value;
157+
}
158+
});
120159

121-
// Prevents the second contextType+contextTypes access from leaving its hooks around
122-
Object.defineProperty(component, "getDerivedStateFromProps", {
123-
configurable: true,
124-
get: function () {
125-
loggingEnabled && logger.debug("get getDerivedStateFromProps", this, stubsApplied, renderHookStep);
126-
loggingEnabled && console.trace("getDerivedStateFromProps trace");
127-
if (renderHookStep == 2) {
128-
renderHookStep = 0;
129-
removeStubsIfNeeded();
160+
// Always accessed directly after contextType for the path we want to catch.
161+
Object.defineProperty(component, "contextTypes", {
162+
configurable: true,
163+
get: function () {
164+
loggingEnabled && logger.debug("get contexttypes", this, stubsApplied, renderHookStep);
165+
loggingEnabled && console.trace("contextTypes trace");
166+
if (renderHookStep == 1) {
167+
renderHookStep = 2;
168+
applyStubsIfNeeded();
169+
};
170+
return this._contextTypes;
171+
},
172+
set: function (value) {
173+
this._contextTypes = value;
130174
}
131-
return this._getDerivedStateFromProps;
132-
},
133-
set: function (value) {
134-
this._getDerivedStateFromProps = value;
135-
}
136-
});
175+
});
176+
177+
// Set directly after class is instantiated
178+
Object.defineProperty(component.prototype, "updater", {
179+
configurable: true,
180+
get: function () {
181+
return this._updater;
182+
},
183+
set: function (value) {
184+
loggingEnabled && logger.debug("set updater", this, value, stubsApplied, renderHookStep);
185+
loggingEnabled && console.trace("updater trace");
186+
if (renderHookStep == 2) {
187+
renderHookStep = 0;
188+
removeStubsIfNeeded();
189+
}
190+
return this._updater = value;
191+
}
192+
});
193+
194+
// Prevents the second contextType+contextTypes access from leaving its hooks around
195+
Object.defineProperty(component, "getDerivedStateFromProps", {
196+
configurable: true,
197+
get: function () {
198+
loggingEnabled && logger.debug("get getDerivedStateFromProps", this, stubsApplied, renderHookStep);
199+
loggingEnabled && console.trace("getDerivedStateFromProps trace");
200+
if (renderHookStep == 2) {
201+
renderHookStep = 0;
202+
removeStubsIfNeeded();
203+
}
204+
return this._getDerivedStateFromProps;
205+
},
206+
set: function (value) {
207+
this._getDerivedStateFromProps = value;
208+
}
209+
});
210+
}
137211

138212
return userComponent;
139213
}

src/utils/react/react.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import * as React from 'react';
1+
import type * as React from 'react';
2+
import type * as ReactDOM from 'react-dom';
3+
import type * as JSXRuntime from 'react/jsx-runtime';
24
import { Ref, useState } from 'react';
35

46
// this shouldn't need to be redeclared but it does for some reason
57

68
declare global {
79
interface Window {
810
SP_REACT: typeof React;
11+
SP_REACTDOM: typeof ReactDOM;
12+
SP_JSX: typeof JSXRuntime;
913
}
1014
}
1115

@@ -33,9 +37,11 @@ export function createPropListRegex(propList: string[], fromStart: boolean = tru
3337

3438
let oldHooks = {};
3539

40+
export let INTERNAL_HOOKS = (window.SP_REACT as any)?.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?.ReactCurrentDispatcher
41+
.current || Object.values((window.SP_REACT as any)?.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE).find((p: any) => p?.useEffect);
42+
3643
export function applyHookStubs(customHooks: any = {}): any {
37-
const hooks = (window.SP_REACT as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher
38-
.current;
44+
const hooks = INTERNAL_HOOKS;
3945

4046
// TODO: add more hooks
4147

@@ -50,7 +56,7 @@ export function applyHookStubs(customHooks: any = {}): any {
5056
};
5157

5258
hooks.useCallback = (cb: Function) => cb;
53-
hooks.useContext = (cb: any) => cb._currentValue;
59+
hooks.useContext = (cb: any) => cb?._currentValue;
5460
hooks.useLayoutEffect = (_: Function) => {}; //cb();
5561
hooks.useMemo = (cb: Function, _: any[]) => cb;
5662
hooks.useEffect = (_: Function) => {}; //cb();
@@ -67,8 +73,7 @@ export function applyHookStubs(customHooks: any = {}): any {
6773
}
6874

6975
export function removeHookStubs() {
70-
const hooks = (window.SP_REACT as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher
71-
.current;
76+
const hooks = INTERNAL_HOOKS;
7277
Object.assign(hooks, oldHooks);
7378
oldHooks = {};
7479
}

tsconfig.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
"outDir": "dist",
44
"module": "ESNext",
55
"target": "ES2020",
6-
"jsx": "react",
7-
"jsxFactory": "window.SP_REACT.createElement",
6+
"jsx": "react-jsx",
87
"declaration": true,
98
"moduleResolution": "node",
109
"noUnusedLocals": true,

0 commit comments

Comments
 (0)