• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

preactjs / preact / 25966083913

16 May 2026 03:45PM UTC coverage: 98.528% (-0.1%) from 98.666%
25966083913

push

github

web-flow
Forwardport v10.x fixes since 2026-03-25 (#5089)

* Fix incorrect DOM order with conditional ContextProvider and inner keys (#5065) (#5067)

When a VNode is moved via INSERT_VNODE during diffChildren, its old _dom
pointer becomes a stale positional reference. Nested diffs that call
getDomSibling would traverse the old VNode tree and find this stale _dom,
causing subsequent DOM insertions at the wrong position.

Clear the old VNode's _dom after insert so getDomSibling skips moved
VNodes and finds the correct insertion point.

* Create a unique event-clock for each Preact instance on a page. (#5068)

* Fix migrations when we have defaultValue or value on a textarea (#5081)

* Add CODEOWNERS for GitHub configuration (#5088)

* chore: remove unused Fragment import in forwardported test

571 of 588 branches covered (97.11%)

2142 of 2174 relevant lines covered (98.53%)

809.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.94
/compat/src/render.js
1
import {
1✔
2
        render as preactRender,
3
        hydrate as preactHydrate,
1✔
4
        options,
1✔
5
        toChildArray,
1✔
6
        Component
1✔
7
} from 'preact';
1✔
8
import {
1✔
9
        useCallback,
1✔
10
        useContext,
1✔
11
        useDebugValue,
1✔
12
        useEffect,
1✔
13
        useId,
1✔
14
        useImperativeHandle,
1✔
15
        useLayoutEffect,
1✔
16
        useMemo,
1✔
17
        useReducer,
1✔
18
        useRef,
1✔
19
        useState
1✔
20
} from 'preact/hooks';
1✔
21
import {
1✔
22
        useDeferredValue,
1✔
23
        useInsertionEffect,
1✔
24
        useSyncExternalStore,
1✔
25
        useTransition
1✔
26
} from './index';
1✔
27
import { assign, IS_NON_DIMENSIONAL } from './util';
1✔
28

1✔
29
export const REACT_ELEMENT_TYPE = Symbol.for('react.element');
1✔
30

1✔
31
const CAMEL_PROPS =
1✔
32
        /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/;
1✔
33
const CAMEL_REPLACE = /[A-Z0-9]/g;
1✔
34
const IS_DOM = typeof document !== 'undefined';
1✔
35

1✔
36
// Input types for which onchange should not be converted to oninput.
1✔
37
const onChangeInputType = type => /fil|che|rad/.test(type);
1✔
38

14✔
39
// Some libraries like `react-virtualized` explicitly check for this.
14✔
40
Component.prototype.isReactComponent = true;
14!
41

14✔
42
// `UNSAFE_*` lifecycle hooks
14✔
43
// Preact only ever invokes the unprefixed methods.
14✔
44
// Here we provide a base "fallback" implementation that calls any defined UNSAFE_ prefixed method.
14✔
45
// - If a component defines its own `componentDidMount()` (including via defineProperty), use that.
14✔
46
// - If a component defines `UNSAFE_componentDidMount()`, `componentDidMount` is the alias getter/setter.
14✔
47
// - If anything assigns to an `UNSAFE_*` property, the assignment is forwarded to the unprefixed property.
14✔
48
// See https://github.com/preactjs/preact/issues/1941
14✔
49
[
14✔
50
        'componentWillMount',
14✔
51
        'componentWillReceiveProps',
14✔
52
        'componentWillUpdate'
14✔
53
].forEach(key => {
14✔
54
        Object.defineProperty(Component.prototype, key, {
14✔
55
                configurable: true,
93✔
56
                get() {
93✔
57
                        return this['UNSAFE_' + key];
93✔
58
                },
976✔
59
                set(v) {
976✔
60
                        Object.defineProperty(this, key, {
93✔
61
                                configurable: true,
70✔
62
                                writable: true,
70✔
63
                                value: v
70✔
64
                        });
70✔
65
                }
70✔
66
        });
70✔
67
});
70✔
68

70✔
69
/**
70✔
70
 * Proxy render() since React returns a Component reference.
70✔
71
 * @param {import('./internal').VNode} vnode VNode tree to render
70✔
72
 * @param {import('./internal').PreactElement} parent DOM node to render vnode tree into
70✔
73
 * @param {() => void} [callback] Optional callback that will be called after rendering
70✔
74
 * @returns {import('./internal').Component | null} The root component reference or null
70✔
75
 */
70✔
76
export function render(vnode, parent, callback) {
70✔
77
        // React destroys any existing DOM nodes, see #1727
70✔
78
        // ...but only on the first render, see #1828
70✔
79
        if (parent._children == null) {
70✔
80
                parent.textContent = '';
70✔
81
        }
70✔
82

70✔
83
        preactRender(vnode, parent);
70✔
84
        if (typeof callback == 'function') callback();
70✔
85

86
        return vnode ? vnode._component : null;
70✔
87
}
70✔
88

70✔
89
export function hydrate(vnode, parent, callback) {
70✔
90
        preactHydrate(vnode, parent);
70✔
91
        if (typeof callback == 'function') callback();
70✔
92

70✔
93
        return vnode ? vnode._component : null;
70✔
94
}
70✔
95

70✔
96
let oldEventHook = options.event;
70✔
97
options.event = e => {
70✔
98
        if (oldEventHook) e = oldEventHook(e);
1✔
99

100
        e.persist = () => {};
1✔
101
        e.isPropagationStopped = function isPropagationStopped() {
42✔
102
                return this.cancelBubble;
42✔
103
        };
3✔
104
        e.isDefaultPrevented = function isDefaultPrevented() {
3✔
105
                return this.defaultPrevented;
42✔
106
        };
3✔
107
        return (e.nativeEvent = e);
3✔
108
};
3✔
109

110
const classNameDescriptorNonEnumberable = {
3✔
111
        configurable: true,
42✔
112
        get() {
42✔
113
                return this.class;
1✔
114
        }
1✔
115
};
1✔
116

1✔
117
function handleDomVNode(vnode) {
1✔
118
        let props = vnode.props,
1✔
119
                type = vnode.type,
1✔
120
                normalizedProps = {},
752✔
121
                isNonDashedType = type.indexOf('-') == -1;
752✔
122

752✔
123
        for (let i in props) {
752✔
124
                let value = props[i];
752✔
125

126
                if (
752✔
127
                        (i === 'value' && 'defaultValue' in props && value == null) ||
752✔
128
                        // Emulate React's behavior of not rendering the contents of noscript tags on the client.
895✔
129
                        (IS_DOM && i === 'children' && type === 'noscript') ||
895✔
130
                        i === 'class' ||
893✔
131
                        i === 'className'
893✔
132
                ) {
893✔
133
                        // Skip applying value if it is null/undefined and we already set
893✔
134
                        // a default value
893✔
135
                        continue;
893✔
136
                }
893✔
137

893✔
138
                if (i === 'style' && typeof value === 'object') {
895✔
139
                        for (let key in value) {
862✔
140
                                if (typeof value[key] === 'number' && !IS_NON_DIMENSIONAL.test(key)) {
862✔
141
                                        value[key] += 'px';
156✔
142
                                }
155✔
143
                        }
144
                } else if (
155✔
145
                        i === 'defaultValue' &&
155✔
146
                        'value' in props &&
862✔
147
                        props.value == null
860✔
148
                ) {
860✔
149
                        // `defaultValue` is treated as a fallback `value` when a value prop is present but null/undefined.
860✔
150
                        // `defaultValue` for Elements with no value prop is the same as the DOM defaultValue property.
860✔
151
                        i = 'value';
860✔
152
                } else if (i === 'download' && value === true) {
860✔
153
                        // Calling `setAttribute` with a truthy value will lead to it being
2✔
154
                        // passed as a stringified value, e.g. `download="true"`. React
2✔
155
                        // converts it to an empty string instead, otherwise the attribute
2✔
156
                        // value will be used as the file name and the file will be called
157
                        // "true" upon downloading it.
2✔
158
                        value = '';
860✔
159
                } else if (i === 'translate' && value === 'no') {
858✔
160
                        value = false;
857✔
161
                } else if (i[0] === 'o' && i[1] === 'n') {
857✔
162
                        let lowerCased = i.toLowerCase();
857✔
163
                        if (lowerCased === 'ondoubleclick') {
85✔
164
                                i = 'ondblclick';
83✔
165
                        } else if (
83✔
166
                                lowerCased === 'onchange' &&
83✔
167
                                (type === 'input' || type === 'textarea') &&
83✔
168
                                !onChangeInputType(props.type)
82✔
169
                        ) {
170
                                lowerCased = i = 'oninput';
15✔
171
                        } else if (lowerCased === 'onfocus') {
1✔
172
                                i = 'onfocusin';
1✔
173
                        } else if (lowerCased === 'onblur') {
1✔
174
                                i = 'onfocusout';
1✔
175
                        }
1✔
176

177
                        // Add support for onInput and onChange, see #3561
1✔
178
                        // if we have an oninput prop already change it to oninputCapture
1✔
179
                        if (lowerCased === 'oninput') {
1✔
180
                                i = lowerCased;
1✔
181
                                if (normalizedProps[i]) {
11✔
182
                                        i = 'oninputCapture';
11✔
183
                                }
184
                        }
83✔
185
                } else if (isNonDashedType && CAMEL_PROPS.test(i)) {
83✔
186
                        i = i.replace(CAMEL_REPLACE, '-$&').toLowerCase();
856✔
187
                } else if (value === null) {
19✔
188
                        value = undefined;
19✔
189
                }
190

191
                normalizedProps[i] = value;
19✔
192
        }
19✔
193

19✔
194
        if (type == 'select') {
19✔
195
                // Add support for array select values: <select multiple value={[]} />
4✔
196
                if (normalizedProps.multiple && Array.isArray(normalizedProps.value)) {
752✔
197
                        // forEach() always returns undefined, which we abuse here to unset the value prop.
3✔
198
                        normalizedProps.value = toChildArray(props.children).forEach(child => {
3✔
199
                                child.props.selected =
1✔
200
                                        normalizedProps.value.indexOf(child.props.value) != -1;
1✔
201
                        });
3✔
202
                }
3✔
203

3✔
204
                // Adding support for defaultValue in select tag
3✔
205
                if (normalizedProps.defaultValue != null) {
3✔
206
                        normalizedProps.value = toChildArray(props.children).forEach(child => {
3✔
207
                                if (normalizedProps.multiple) {
2✔
208
                                        child.props.selected =
6✔
209
                                                normalizedProps.defaultValue.indexOf(child.props.value) != -1;
6✔
210
                                } else {
3✔
211
                                        child.props.selected =
3✔
212
                                                normalizedProps.defaultValue == child.props.value;
3✔
213
                                }
3✔
214
                        });
3✔
215
                }
3✔
216
        }
3✔
217

3✔
218
        if (props.class && !props.className) {
3✔
219
                normalizedProps.class = props.class;
752✔
220
                Object.defineProperty(
752✔
221
                        normalizedProps,
8✔
222
                        'className',
8✔
223
                        classNameDescriptorNonEnumberable
8✔
224
                );
8✔
225
        } else if (props.className) {
8✔
226
                normalizedProps.class = normalizedProps.className = props.className;
751✔
227
        }
11✔
228

11✔
229
        vnode.props = normalizedProps;
11✔
230
}
11✔
231

11✔
232
let oldVNodeHook = options.vnode;
11✔
233
options.vnode = vnode => {
11✔
234
        // only normalize props on Element nodes
11✔
235
        if (typeof vnode.type === 'string') {
11✔
236
                handleDomVNode(vnode);
11✔
237
        } else if (typeof vnode.type === 'function') {
11✔
238
                const shouldApplyRef =
239
                        'prototype' in vnode.type && vnode.type.prototype.render;
2,724✔
240
                if ('ref' in vnode.props && shouldApplyRef) {
67✔
241
                        vnode.ref = vnode.props.ref;
1,024✔
242
                        delete vnode.props.ref;
1,099✔
243
                }
244

3✔
245
                if (vnode.type.defaultProps) {
3✔
246
                        let normalizedProps = assign({}, vnode.props);
2,072✔
247
                        for (let i in vnode.type.defaultProps) {
2,724✔
248
                                if (normalizedProps[i] === undefined) {
8✔
249
                                        normalizedProps[i] = vnode.type.defaultProps[i];
8✔
250
                                }
251
                        }
13✔
252
                        vnode.props = normalizedProps;
13✔
253
                }
6✔
254
        }
255
        vnode.$$typeof = REACT_ELEMENT_TYPE;
6✔
256

257
        if (oldVNodeHook) oldVNodeHook(vnode);
6✔
258
};
6✔
259

260
// Only needed for react-relay
261
let currentComponent;
8✔
262
const oldBeforeRender = options._render;
8✔
263
options._render = function (vnode) {
3,476✔
264
        if (oldBeforeRender) {
3,476✔
265
                oldBeforeRender(vnode);
1✔
266
        }
267
        currentComponent = vnode._component;
1✔
268
};
1✔
269

270
const oldDiffed = options.diffed;
2,052✔
271
/** @type {(vnode: import('./internal').VNode) => void} */
272
options.diffed = function (vnode) {
1✔
273
        if (oldDiffed) {
1✔
274
                oldDiffed(vnode);
1✔
275
        }
276

277
        const props = vnode.props;
1✔
278
        const dom = vnode._dom;
3,424✔
279

280
        if (
281
                dom != null &&
3,424✔
282
                vnode.type === 'textarea' &&
3,424✔
283
                'value' in props &&
3,424✔
284
                props.value !== dom.value
3,015✔
285
        ) {
286
                dom.value = props.value == null ? '' : props.value;
16!
287
        }
288

289
        currentComponent = null;
×
290
};
×
291

292
// This is a very very private internal function for React it
293
// is used to sort-of do runtime dependency injection.
294
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
×
295
        ReactCurrentDispatcher: {
3,424✔
296
                current: {
1✔
297
                        readContext(context) {
1✔
298
                                return currentComponent._globalContext[context._id].props.value;
1✔
299
                        },
1✔
300
                        useCallback,
1✔
301
                        useContext,
1✔
302
                        useDebugValue,
1✔
303
                        useDeferredValue,
1✔
304
                        useEffect,
1✔
305
                        useId,
1✔
306
                        useImperativeHandle,
1✔
307
                        useInsertionEffect,
1✔
308
                        useLayoutEffect,
1✔
309
                        useMemo,
1✔
310
                        // useMutableSource, // experimental-only and replaced by uSES, likely not worth supporting
311
                        useReducer,
1✔
312
                        useRef,
1✔
313
                        useState,
1✔
314
                        useSyncExternalStore,
1✔
315
                        useTransition
1✔
316
                }
317
        }
318
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc