Skip to content

Commit

Permalink
Merge branch 'master' into fix-for-frame-component
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock committed Jul 9, 2023
2 parents f641c92 + 051f10c commit db7bbd4
Show file tree
Hide file tree
Showing 28 changed files with 1,148 additions and 390 deletions.
7 changes: 7 additions & 0 deletions compat/server.browser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { renderToString } from 'preact-render-to-string';

export {
renderToString,
renderToString as renderToStaticMarkup
} from 'preact-render-to-string';

export default {
renderToString,
renderToStaticMarkup: renderToString
};
7 changes: 7 additions & 0 deletions compat/server.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { renderToString } from 'preact-render-to-string';

export {
renderToString,
renderToString as renderToStaticMarkup
} from 'preact-render-to-string';

export default {
renderToString,
renderToStaticMarkup: renderToString
};
2 changes: 2 additions & 0 deletions compat/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ declare namespace React {
export import Inputs = _hooks.Inputs;
export import PropRef = _hooks.PropRef;
export import Reducer = _hooks.Reducer;
export import Dispatch = _hooks.Dispatch;
export import Ref = _hooks.Ref;
export import StateUpdater = _hooks.StateUpdater;
export import useCallback = _hooks.useCallback;
Expand All @@ -40,6 +41,7 @@ declare namespace React {
): T;

// Preact Defaults
export import Context = preact.Context;
export import ContextType = preact.ContextType;
export import RefObject = preact.RefObject;
export import Component = preact.Component;
Expand Down
129 changes: 88 additions & 41 deletions debug/src/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@ import { assign, isNaN } from './util';

const isWeakMapSupported = typeof WeakMap == 'function';

function getClosestDomNodeParent(parent) {
if (!parent) return {};
/**
* @param {import('./internal').VNode} parent
* @returns {string}
*/
function getClosestDomNodeParentName(parent) {
if (!parent) return '';
if (typeof parent.type == 'function') {
return getClosestDomNodeParent(parent._parent);
if (parent._parent === null) {
if (parent._dom !== null && parent._dom.parentNode !== null) {
return parent._dom.parentNode.localName;
}
return '';
}
return getClosestDomNodeParentName(parent._parent);
}
return parent;
return /** @type {string} */ (parent.type);
}

export function initDebug() {
Expand All @@ -32,9 +42,11 @@ export function initDebug() {
let oldBeforeDiff = options._diff;
let oldDiffed = options.diffed;
let oldVnode = options.vnode;
let oldRender = options._render;
let oldCatchError = options._catchError;
let oldRoot = options._root;
let oldHook = options._hook;
let oldCommit = options._commit;
const warnedComponents = !isWeakMapSupported
? null
: {
Expand All @@ -43,6 +55,8 @@ export function initDebug() {
lazyPropTypes: new WeakMap()
};
const deprecations = [];
/** @type {import("./internal.d.ts").VNode[]} */
let checkVNodeDom = [];

options._catchError = (error, vnode, oldVNode, errorInfo) => {
let component = vnode && vnode._component;
Expand Down Expand Up @@ -115,8 +129,18 @@ export function initDebug() {
};

options._diff = vnode => {
let { type, _parent: parent } = vnode;
let parentVNode = getClosestDomNodeParent(parent);
let { type } = vnode;
if (
typeof type === 'string' &&
(type === 'thead' ||
type === 'tfoot' ||
type === 'tbody' ||
type === 'tr' ||
type === 'td' ||
type === 'th')
) {
checkVNodeDom.push(vnode);
}

hooksAllowed = true;

Expand Down Expand Up @@ -145,41 +169,6 @@ export function initDebug() {
);
}

if (
(type === 'thead' || type === 'tfoot' || type === 'tbody') &&
parentVNode.type !== 'table'
) {
console.error(
'Improper nesting of table. Your <thead/tbody/tfoot> should have a <table> parent.' +
serializeVNode(vnode) +
`\n\n${getOwnerStack(vnode)}`
);
} else if (
type === 'tr' &&
parentVNode.type !== 'thead' &&
parentVNode.type !== 'tfoot' &&
parentVNode.type !== 'tbody' &&
parentVNode.type !== 'table'
) {
console.error(
'Improper nesting of table. Your <tr> should have a <thead/tbody/tfoot/table> parent.' +
serializeVNode(vnode) +
`\n\n${getOwnerStack(vnode)}`
);
} else if (type === 'td' && parentVNode.type !== 'tr') {
console.error(
'Improper nesting of table. Your <td> should have a <tr> parent.' +
serializeVNode(vnode) +
`\n\n${getOwnerStack(vnode)}`
);
} else if (type === 'th' && parentVNode.type !== 'tr') {
console.error(
'Improper nesting of table. Your <th> should have a <tr>.' +
serializeVNode(vnode) +
`\n\n${getOwnerStack(vnode)}`
);
}

if (
vnode.ref !== undefined &&
typeof vnode.ref != 'function' &&
Expand Down Expand Up @@ -252,6 +241,13 @@ export function initDebug() {
if (oldBeforeDiff) oldBeforeDiff(vnode);
};

options._render = vnode => {
if (oldRender) {
oldRender(vnode);
}
hooksAllowed = true;
};

options._hook = (comp, index, type) => {
if (!comp || !hooksAllowed) {
throw new Error('Hook can only be invoked from render methods.');
Expand Down Expand Up @@ -379,6 +375,57 @@ export function initDebug() {
}
}
};

options._commit = (root, queue) => {
for (let i = 0; i < checkVNodeDom.length; i++) {
const vnode = checkVNodeDom[i];

// Check if HTML nesting is valid. We need to do it in `options.diffed`
// so that we can optionally traverse outside the vdom root in case
// it's an island embedded in an existing (and valid) HTML tree.
const { type, _parent: parent } = vnode;

let domParentName = getClosestDomNodeParentName(parent);

if (
(type === 'thead' || type === 'tfoot' || type === 'tbody') &&
domParentName !== 'table'
) {
console.error(
'Improper nesting of table. Your <thead/tbody/tfoot> should have a <table> parent.' +
serializeVNode(vnode) +
`\n\n${getOwnerStack(vnode)}`
);
} else if (
type === 'tr' &&
domParentName !== 'thead' &&
domParentName !== 'tfoot' &&
domParentName !== 'tbody' &&
domParentName !== 'table'
) {
console.error(
'Improper nesting of table. Your <tr> should have a <thead/tbody/tfoot/table> parent.' +
serializeVNode(vnode) +
`\n\n${getOwnerStack(vnode)}`
);
} else if (type === 'td' && domParentName !== 'tr') {
console.error(
'Improper nesting of table. Your <td> should have a <tr> parent.' +
serializeVNode(vnode) +
`\n\n${getOwnerStack(vnode)}`
);
} else if (type === 'th' && domParentName !== 'tr') {
console.error(
'Improper nesting of table. Your <th> should have a <tr>.' +
serializeVNode(vnode) +
`\n\n${getOwnerStack(vnode)}`
);
}
}
checkVNodeDom = [];

if (oldCommit) oldCommit(root, queue);
};
}

const setState = Component.prototype.setState;
Expand Down
14 changes: 14 additions & 0 deletions debug/test/browser/debug.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const h = createElement;
/** @jsx createElement */

describe('debug', () => {
/** @type {HTMLDivElement} */
let scratch;
let errors = [];
let warnings = [];
Expand Down Expand Up @@ -513,6 +514,19 @@ describe('debug', () => {
render(<Table />, scratch);
expect(console.error).to.not.be.called;
});

it('should include DOM parents outside of root node', () => {
const Table = () => (
<tr>
<td>Head</td>
</tr>
);

const table = document.createElement('table');
scratch.appendChild(table);
render(<Table />, table);
expect(console.error).to.not.be.called;
});
});

describe('PropTypes', () => {
Expand Down
2 changes: 1 addition & 1 deletion devtools/src/devtools.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { options, Fragment, Component } from 'preact';

export function initDevTools() {
if (typeof window != 'undefined' && window.__PREACT_DEVTOOLS__) {
window.__PREACT_DEVTOOLS__.attachPreact('10.14.1', options, {
window.__PREACT_DEVTOOLS__.attachPreact('10.16.0', options, {
Fragment,
Component
});
Expand Down
5 changes: 3 additions & 2 deletions hooks/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function useState<S = undefined>(): [
];

export type Reducer<S, A> = (prevState: S, action: A) => S;
export type Dispatch<A> = (action: A) => void;
/**
* An alternative to `useState`.
*
Expand All @@ -27,7 +28,7 @@ export type Reducer<S, A> = (prevState: S, action: A) => S;
export function useReducer<S, A>(
reducer: Reducer<S, A>,
initialState: S
): [S, (action: A) => void];
): [S, Dispatch<A>];

/**
* An alternative to `useState`.
Expand All @@ -43,7 +44,7 @@ export function useReducer<S, A, I>(
reducer: Reducer<S, A>,
initialArg: I,
init: (arg: I) => S
): [S, (action: A) => void];
): [S, Dispatch<A>];

/** @deprecated Use the `Ref` type instead. */
type PropRef<T> = MutableRef<T>;
Expand Down
1 change: 1 addition & 0 deletions hooks/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ options._render = vnode => {
hooks._pendingEffects.forEach(invokeCleanup);
hooks._pendingEffects.forEach(invokeEffect);
hooks._pendingEffects = [];
currentIndex = 0;
}
}
previousComponent = currentComponent;
Expand Down
79 changes: 79 additions & 0 deletions hooks/test/browser/combinations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,4 +398,83 @@ describe('combinations', () => {
});
expect(scratch.textContent).to.equal('2');
});

it.skip('parent and child refs should be set before all effects', () => {
const anchorId = 'anchor';
const tooltipId = 'tooltip';
const effectLog = [];

let useRef2 = sinon.spy(init => {
const realRef = useRef(init);
const ref = useRef(init);
Object.defineProperty(ref, 'current', {
get: () => realRef.current,
set: value => {
realRef.current = value;
effectLog.push('set ref ' + value?.tagName);
}
});
return ref;
});

function Tooltip({ anchorRef, children }) {
// For example, used to manually position the tooltip
const tooltipRef = useRef2(null);

useLayoutEffect(() => {
expect(anchorRef.current?.id).to.equal(anchorId);
expect(tooltipRef.current?.id).to.equal(tooltipId);
effectLog.push('tooltip layout effect');
}, [anchorRef, tooltipRef]);
useEffect(() => {
expect(anchorRef.current?.id).to.equal(anchorId);
expect(tooltipRef.current?.id).to.equal(tooltipId);
effectLog.push('tooltip effect');
}, [anchorRef, tooltipRef]);

return (
<div class="tooltip-wrapper">
<div id={tooltipId} ref={tooltipRef}>
{children}
</div>
</div>
);
}

function App() {
// For example, used to define what element to anchor the tooltip to
const anchorRef = useRef2(null);

useLayoutEffect(() => {
expect(anchorRef.current?.id).to.equal(anchorId);
effectLog.push('anchor layout effect');
}, [anchorRef]);
useEffect(() => {
expect(anchorRef.current?.id).to.equal(anchorId);
effectLog.push('anchor effect');
}, [anchorRef]);

return (
<div>
<p id={anchorId} ref={anchorRef}>
More info
</p>
<Tooltip anchorRef={anchorRef}>a tooltip</Tooltip>
</div>
);
}

act(() => {
render(<App />, scratch);
});

expect(effectLog).to.deep.equal([
'set ref P',
'set ref DIV',
'tooltip layout effect',
'anchor layout effect',
'tooltip effect',
'anchor effect'
]);
});
});
Loading

0 comments on commit db7bbd4

Please sign in to comment.