Skip to content

Commit

Permalink
Fix "Invalid hook call" warning (#3769)
Browse files Browse the repository at this point in the history
* Fix "Invalid hook call" warning

* Fix eslint warnings

* Apply code review suggestions
  • Loading branch information
hippotastic authored Jun 30, 2022
1 parent 38f2fd7 commit b934ab5
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/lazy-countries-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/preact': patch
---

Fix "Invalid hook call" warning
83 changes: 74 additions & 9 deletions packages/integrations/preact/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,34 @@ import StaticHtml from './static-html.js';

const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());

let originalConsoleError;
let consoleFilterRefs = 0;

function check(Component, props, children) {
if (typeof Component !== 'function') return false;

if (Component.prototype != null && typeof Component.prototype.render === 'function') {
return BaseComponent.isPrototypeOf(Component);
}

useConsoleFilter();

try {
const { html } = renderToStaticMarkup(Component, props, children);
if (typeof html !== 'string') {
return false;
}
try {
const { html } = renderToStaticMarkup(Component, props, children);
if (typeof html !== 'string') {
return false;
}

// There are edge cases (SolidJS) where Preact *might* render a string,
// but components would be <undefined></undefined>
// There are edge cases (SolidJS) where Preact *might* render a string,
// but components would be <undefined></undefined>

return !/\<undefined\>/.test(html);
} catch (err) {
return false;
return !/\<undefined\>/.test(html);
} catch (err) {
return false;
}
} finally {
finishUsingConsoleFilter();
}
}

Expand All @@ -40,6 +49,62 @@ function renderToStaticMarkup(Component, props, { default: children, ...slotted
return { html };
}

/**
* Reduces console noise by filtering known non-problematic errors.
*
* Performs reference counting to allow parallel usage from async code.
*
* To stop filtering, please ensure that there always is a matching call
* to `finishUsingConsoleFilter` afterwards.
*/
function useConsoleFilter() {
consoleFilterRefs++;

if (!originalConsoleError) {
// eslint-disable-next-line no-console
originalConsoleError = console.error;

try {
// eslint-disable-next-line no-console
console.error = filteredConsoleError;
} catch (error) {
// If we're unable to hook `console.error`, just accept it
}
}
}

/**
* Indicates that the filter installed by `useConsoleFilter`
* is no longer needed by the calling code.
*/
function finishUsingConsoleFilter() {
consoleFilterRefs--;

// Note: Instead of reverting `console.error` back to the original
// when the reference counter reaches 0, we leave our hook installed
// to prevent potential race conditions once `check` is made async
}

/**
* Hook/wrapper function for the global `console.error` function.
*
* Ignores known non-problematic errors while any code is using the console filter.
* Otherwise, simply forwards all arguments to the original function.
*/
function filteredConsoleError(msg, ...rest) {
if (consoleFilterRefs > 0 && typeof msg === 'string') {
// In `check`, we attempt to render JSX components through Preact.
// When attempting this on a React component, React may output
// the following error, which we can safely filter out:
const isKnownReactHookError =
msg.includes('Warning: Invalid hook call.') &&
msg.includes('https://reactjs.org/link/invalid-hook-call');
if (isKnownReactHookError)
return;
}
originalConsoleError(msg, ...rest);
}

export default {
check,
renderToStaticMarkup,
Expand Down

0 comments on commit b934ab5

Please sign in to comment.