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

preactjs / preact / 30611869639

31 Jul 2026 07:09AM UTC coverage: 97.667% (-1.6%) from 99.291%
30611869639

Pull #5187

github

web-flow
Merge d18a8748c into 47110cbcc
Pull Request #5187: Commit pending hook state in options._render

569 of 587 branches covered (96.93%)

14 of 14 new or added lines in 1 file covered. (100.0%)

34 existing lines in 1 file now uncovered.

2051 of 2100 relevant lines covered (97.67%)

111.45 hits per line

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

87.97
/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 { useDeferredValue, useInsertionEffect, useTransition } from './index';
1✔
22
import { assign, IS_NON_DIMENSIONAL } from './util';
23

1✔
24
export const REACT_ELEMENT_TYPE = Symbol.for('react.element');
1✔
25

1✔
26
const MODE_HYDRATE = 1 << 5;
1✔
27

1✔
28
const CAMEL_PROPS =
1✔
29
        /^(?: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✔
30
const CAMEL_REPLACE = /[A-Z0-9]/g;
1✔
31
const IS_DOM = typeof document !== 'undefined';
1✔
32

33
/**
1✔
34
 * This is taken from https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js#L84
1✔
35
 * on a high level this cuts out the warnings, ... and attempts a smaller implementation
1✔
36
 * @typedef {{ _value: any; _getSnapshot: () => any }} Store
1✔
37
 */
38
export function useSyncExternalStore(
1✔
39
        subscribe,
1✔
40
        getSnapshot,
1✔
41
        getServerSnapshot
1✔
42
) {
1✔
43
        const serverRendering = options._skipEffects || hydrationRoot;
1✔
44
        const value = serverRendering
61✔
45
                ? (getServerSnapshot || getSnapshot)()
61✔
46
                : getSnapshot();
61✔
47

61✔
48
        /**
49
         * @typedef {{ _instance: Store }} StoreRef
61✔
50
         * @type {[StoreRef, (store: StoreRef) => void]}
51
         */
61✔
52
        const [{ _instance }, forceUpdate] = useState({
61✔
53
                _instance: { _value: value, _getSnapshot: getSnapshot }
61✔
54
        });
61✔
55

61✔
56
        useLayoutEffect(() => {
61✔
57
                _instance._value = value;
61✔
58
                _instance._getSnapshot = getSnapshot;
61✔
59

60
                if (didSnapshotChange(_instance)) {
61✔
61
                        forceUpdate({ _instance });
61✔
62
                }
50✔
63
        }, [subscribe, value, getSnapshot]);
50✔
64

50✔
65
        useEffect(() => {
50✔
66
                if (didSnapshotChange(_instance)) {
61✔
67
                        forceUpdate({ _instance });
61✔
68
                }
69

28✔
70
                return subscribe(() => {
28✔
71
                        if (didSnapshotChange(_instance)) {
28✔
72
                                forceUpdate({ _instance });
28✔
73
                        }
3✔
74
                });
3✔
75
        }, [subscribe]);
28✔
76

28✔
77
        return value;
28✔
78
}
28✔
79

28✔
80
/** @type {(inst: Store) => boolean} */
28✔
81
function didSnapshotChange(inst) {
28✔
82
        try {
21✔
83
                return !Object.is(inst._value, inst._getSnapshot());
1✔
84
        } catch (error) {
106✔
85
                return true;
106✔
86
        }
106✔
87
}
106✔
88

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

14✔
92
// Some libraries like `react-virtualized` explicitly check for this.
14✔
93
Component.prototype.isReactComponent = true;
14!
94

14✔
95
// `UNSAFE_*` lifecycle hooks
14✔
96
// Preact only ever invokes the unprefixed methods.
14✔
97
// Here we provide a base "fallback" implementation that calls any defined UNSAFE_ prefixed method.
14✔
98
// - If a component defines its own `componentDidMount()` (including via defineProperty), use that.
14✔
99
// - If a component defines `UNSAFE_componentDidMount()`, `componentDidMount` is the alias getter/setter.
100
// - If anything assigns to an `UNSAFE_*` property, the assignment is forwarded to the unprefixed property.
101
// See https://github.com/preactjs/preact/issues/1941
14✔
102
[
14✔
103
        'componentWillMount',
14✔
104
        'componentWillReceiveProps',
14✔
105
        'componentWillUpdate'
14✔
106
].forEach(key => {
14✔
107
        Object.defineProperty(Component.prototype, key, {
14✔
108
                configurable: true,
96✔
109
                get() {
96✔
110
                        return this['UNSAFE_' + key];
96✔
111
                },
923✔
112
                set(v) {
923✔
113
                        Object.defineProperty(this, key, {
96✔
UNCOV
114
                                configurable: true,
×
UNCOV
115
                                writable: true,
×
UNCOV
116
                                value: v
×
UNCOV
117
                        });
×
UNCOV
118
                }
×
UNCOV
119
        });
×
UNCOV
120
});
×
UNCOV
121

×
UNCOV
122
/**
×
UNCOV
123
 * Proxy render() since React returns a Component reference.
×
UNCOV
124
 * @param {import('./internal').VNode} vnode VNode tree to render
×
125
 * @param {import('./internal').PreactElement} parent DOM node to render vnode tree into
UNCOV
126
 * @param {() => void} [callback] Optional callback that will be called after rendering
×
UNCOV
127
 * @returns {import('./internal').Component | null} The root component reference or null
×
UNCOV
128
 */
×
UNCOV
129
export function render(vnode, parent, callback) {
×
UNCOV
130
        // React destroys any existing DOM nodes, see #1727
×
UNCOV
131
        // ...but only on the first render, see #1828
×
UNCOV
132
        if (parent._children == null) {
✔
UNCOV
133
                parent.textContent = '';
×
UNCOV
134
        }
×
135

UNCOV
136
        preactRender(vnode, parent);
×
UNCOV
137
        if (typeof callback == 'function') callback();
✔
138

UNCOV
139
        return vnode ? vnode._component : null;
×
UNCOV
140
}
×
UNCOV
141

×
UNCOV
142
export function hydrate(vnode, parent, callback) {
✔
UNCOV
143
        preactHydrate(vnode, parent);
✔
UNCOV
144
        if (typeof callback == 'function') callback();
×
UNCOV
145

×
UNCOV
146
        return vnode ? vnode._component : null;
×
UNCOV
147
}
×
UNCOV
148

×
UNCOV
149
let oldEventHook = options.event;
×
UNCOV
150
options.event = e => {
×
151
        if (oldEventHook) e = oldEventHook(e);
1✔
152

1✔
153
        e.persist = () => {};
1✔
154
        e.isPropagationStopped = function isPropagationStopped() {
45✔
155
                return this.cancelBubble;
45✔
156
        };
3✔
157
        e.isDefaultPrevented = function isDefaultPrevented() {
3✔
158
                return this.defaultPrevented;
45✔
159
        };
3✔
160
        return (e.nativeEvent = e);
3✔
161
};
3✔
162

3✔
163
const classNameDescriptorNonEnumberable = {
3✔
164
        configurable: true,
45✔
165
        get() {
1✔
166
                return this.class;
1✔
167
        }
1✔
168
};
169

170
function handleDomVNode(vnode) {
1✔
171
        let props = vnode.props,
1✔
172
                type = vnode.type,
1✔
173
                normalizedProps = {},
654✔
174
                isNonDashedType = type.indexOf('-') == -1;
654✔
175

654✔
176
        for (let i in props) {
654✔
177
                let value = props[i];
654✔
178

654✔
179
                if (
654✔
180
                        (i === 'value' && 'defaultValue' in props && value == null) ||
654✔
181
                        // Emulate React's behavior of not rendering the contents of noscript tags on the client.
780✔
182
                        (IS_DOM && i === 'children' && type === 'noscript') ||
780✔
183
                        i === 'class' ||
778✔
184
                        i === 'className'
778✔
185
                ) {
778✔
186
                        // Skip applying value if it is null/undefined and we already set
778✔
187
                        // a default value
188
                        continue;
189
                }
778✔
190

191
                if (i === 'style' && typeof value === 'object') {
780✔
192
                        let cloned;
747✔
193
                        for (let key in value) {
2✔
194
                                if (typeof value[key] === 'number' && !IS_NON_DIMENSIONAL.test(key)) {
747✔
195
                                        if (!cloned) {
157✔
196
                                                cloned = value = assign({}, value);
157✔
197
                                        }
198
                                        value[key] += 'px';
157✔
199
                                }
157✔
200
                        }
157✔
201
                } else if (
157✔
202
                        i === 'defaultValue' &&
2✔
203
                        'value' in props &&
747✔
204
                        props.value == null
745✔
205
                ) {
745✔
206
                        // `defaultValue` is treated as a fallback `value` when a value prop is present but null/undefined.
745✔
207
                        // `defaultValue` for Elements with no value prop is the same as the DOM defaultValue property.
745✔
208
                        i = 'value';
745✔
209
                } else if (i === 'download' && value === true) {
745✔
210
                        // Calling `setAttribute` with a truthy value will lead to it being
2✔
211
                        // passed as a stringified value, e.g. `download="true"`. React
2✔
212
                        // converts it to an empty string instead, otherwise the attribute
2✔
213
                        // value will be used as the file name and the file will be called
2✔
214
                        // "true" upon downloading it.
2✔
215
                        value = '';
745✔
216
                } else if (i === 'translate' && value === 'no') {
743✔
217
                        value = false;
742✔
218
                } else if (i[0] === 'o' && i[1] === 'n') {
742✔
219
                        let lowerCased = i.toLowerCase();
742✔
220
                        if (lowerCased === 'ondoubleclick') {
86✔
221
                                i = 'ondblclick';
84✔
222
                        } else if (
84✔
223
                                lowerCased === 'onchange' &&
84✔
224
                                (type === 'input' || type === 'textarea') &&
84✔
225
                                !onChangeInputType(props.type)
83✔
226
                        ) {
15✔
227
                                lowerCased = i = 'oninput';
15✔
228
                        } else if (lowerCased === 'onfocus') {
1✔
229
                                i = 'onfocusin';
1✔
230
                        } else if (lowerCased === 'onblur') {
1✔
231
                                i = 'onfocusout';
1✔
232
                        }
1✔
233

1✔
234
                        // Add support for onInput and onChange, see #3561
1✔
235
                        // if we have an oninput prop already change it to oninputCapture
1✔
236
                        if (lowerCased === 'oninput') {
1✔
237
                                i = lowerCased;
1✔
238
                                if (normalizedProps[i]) {
11✔
239
                                        i = 'oninputCapture';
11✔
240
                                }
84✔
241
                        }
84✔
242
                } else if (isNonDashedType && CAMEL_PROPS.test(i)) {
84✔
243
                        i = i.replace(CAMEL_REPLACE, '-$&').toLowerCase();
741✔
244
                } else if (value === null) {
19✔
245
                        value = undefined;
19✔
246
                }
19✔
247

19✔
248
                normalizedProps[i] = value;
19✔
249
        }
19✔
250

251
        if (type == 'select') {
19✔
252
                // Add support for array select values: <select multiple value={[]} />
5✔
253
                if (normalizedProps.multiple && Array.isArray(normalizedProps.value)) {
654✔
254
                        // forEach() always returns undefined, which we abuse here to unset the value prop.
255
                        normalizedProps.value = toChildArray(props.children).forEach(child => {
3✔
256
                                child.props.selected =
1✔
257
                                        normalizedProps.value.indexOf(child.props.value) != -1;
1✔
258
                        });
3✔
259
                }
260

261
                // Adding support for defaultValue in select tag
262
                if (normalizedProps.defaultValue != null) {
3✔
263
                        normalizedProps.value = toChildArray(props.children).forEach(child => {
3✔
264
                                if (normalizedProps.multiple) {
2✔
265
                                        child.props.selected =
6✔
266
                                                normalizedProps.defaultValue.indexOf(child.props.value) != -1;
6✔
267
                                } else {
268
                                        child.props.selected =
269
                                                normalizedProps.defaultValue == child.props.value;
3✔
270
                                }
271
                        });
3✔
272
                }
273
        }
274

275
        if (props.class && !props.className) {
3✔
276
                normalizedProps.class = props.class;
654✔
277
                Object.defineProperty(
654✔
278
                        normalizedProps,
8✔
279
                        'className',
8✔
280
                        classNameDescriptorNonEnumberable
8✔
281
                );
282
        } else if (props.className) {
8✔
283
                normalizedProps.class = normalizedProps.className = props.className;
653✔
284
        }
285

286
        vnode.props = normalizedProps;
11✔
287
}
11✔
288

289
let oldVNodeHook = options.vnode;
11✔
290
options.vnode = vnode => {
11✔
291
        // only normalize props on Element nodes
292
        if (typeof vnode.type === 'string') {
11✔
293
                handleDomVNode(vnode);
11✔
294
        } else if (typeof vnode.type === 'function') {
11✔
295
                const shouldApplyRef =
296
                        'prototype' in vnode.type && vnode.type.prototype.render;
2,475✔
297
                if ('ref' in vnode.props && shouldApplyRef) {
67✔
298
                        vnode.ref = vnode.props.ref;
1,035✔
299
                        delete vnode.props.ref;
1,106✔
300
                }
301

302
                if (vnode.type.defaultProps) {
3✔
303
                        let normalizedProps = assign({}, vnode.props);
1,898✔
304
                        for (let i in vnode.type.defaultProps) {
2,475✔
305
                                if (normalizedProps[i] === undefined) {
8✔
306
                                        normalizedProps[i] = vnode.type.defaultProps[i];
8✔
307
                                }
308
                        }
309
                        vnode.props = normalizedProps;
13✔
310
                }
6✔
311
        }
312
        vnode.$$typeof = REACT_ELEMENT_TYPE;
6✔
313

314
        if (oldVNodeHook) oldVNodeHook(vnode);
6✔
315
};
6✔
316

317
// Only needed for react-relay
318
let currentComponent, hydrationRoot;
8✔
319
const oldBeforeRender = options._render;
8✔
320
options._render = function (vnode) {
3,129✔
321
        if (oldBeforeRender) {
3,129✔
322
                oldBeforeRender(vnode);
1✔
323
        }
324
        if (vnode._flags & MODE_HYDRATE) hydrationRoot = vnode;
1✔
325
        currentComponent = vnode._component;
1,876✔
326
};
1,876✔
327

328
const oldDiffed = options.diffed;
1,876✔
329
/** @type {(vnode: import('./internal').VNode) => void} */
330
options.diffed = function (vnode) {
1✔
331
        if (oldDiffed) {
1✔
332
                oldDiffed(vnode);
1✔
333
        }
334

335
        const props = vnode.props;
1✔
336
        const dom = vnode._dom;
3,064✔
337

338
        if (
339
                dom != null &&
3,064✔
340
                vnode.type === 'textarea' &&
3,064✔
341
                'value' in props &&
3,064✔
342
                props.value !== dom.value
2,724✔
343
        ) {
344
                dom.value = props.value == null ? '' : props.value;
1,407!
345
        }
346

347
        currentComponent = null;
×
348
        if (hydrationRoot == vnode) hydrationRoot = null;
×
349
};
×
350

351
// This is a very very private internal function for React it
352
// is used to sort-of do runtime dependency injection.
353
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
×
354
        ReactCurrentDispatcher: {
3,064✔
355
                current: {
1✔
356
                        readContext(context) {
1✔
357
                                return currentComponent._globalContext[context._id].props.value;
1✔
358
                        },
1✔
359
                        useCallback,
1✔
360
                        useContext,
1✔
361
                        useDebugValue,
1✔
362
                        useDeferredValue,
1✔
363
                        useEffect,
1✔
364
                        useId,
1✔
365
                        useImperativeHandle,
1✔
366
                        useInsertionEffect,
1✔
367
                        useLayoutEffect,
1✔
368
                        useMemo,
1✔
369
                        // useMutableSource, // experimental-only and replaced by uSES, likely not worth supporting
370
                        useReducer,
1✔
371
                        useRef,
1✔
372
                        useState,
1✔
373
                        useSyncExternalStore,
1✔
374
                        useTransition
1✔
375
                }
376
        }
377
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc