From 1584d4cef6e51b33b0bf3337178db7144f69f91e Mon Sep 17 00:00:00 2001 From: Rodrigo Leal Date: Wed, 13 Mar 2024 20:15:50 -0300 Subject: [PATCH] feat: added isMemo to compact to allow compatibility with react-is dependant libraries --- compat/src/index.d.ts | 16 +++++++------ compat/src/index.js | 17 ++++++++++++++ compat/test/browser/isMemo.test.js | 37 ++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 compat/test/browser/isMemo.test.js diff --git a/compat/src/index.d.ts b/compat/src/index.d.ts index 80a930d024..3a054da47c 100644 --- a/compat/src/index.d.ts +++ b/compat/src/index.d.ts @@ -115,14 +115,16 @@ declare namespace React { ) => preact.VNode; export function isValidElement(element: any): boolean; export function isFragment(element: any): boolean; + export function isMemo(element: any): boolean; export function findDOMNode( component: preact.Component | Element ): Element | null; - export abstract class PureComponent

extends preact.Component< - P, - S - > { + export abstract class PureComponent< + P = {}, + S = {}, + SS = any + > extends preact.Component { isPureReactComponent: boolean; } @@ -174,9 +176,9 @@ declare namespace React { export type ComponentPropsWithRef< C extends ComponentType | keyof JSXInternal.IntrinsicElements - > = C extends (new(props: infer P) => Component) - ? PropsWithoutRef

& RefAttributes> - : ComponentProps; + > = C extends new (props: infer P) => Component + ? PropsWithoutRef

& RefAttributes> + : ComponentProps; export function flushSync(fn: () => R): R; export function flushSync(fn: (a: A) => R, a: A): R; diff --git a/compat/src/index.js b/compat/src/index.js index 830d34dd24..f08b89b03d 100644 --- a/compat/src/index.js +++ b/compat/src/index.js @@ -63,6 +63,21 @@ function isFragment(element) { return isValidElement(element) && element.type === Fragment; } +/** + * Check if the passed element is a Memo node. + * @param {*} element The element to check + * @returns {boolean} + */ +function isMemo(element) { + return ( + !!element && + !!element.displayName && + (typeof element.displayName === 'string' || + element.displayName instanceof String) && + element.displayName.startsWith('Memo(') + ); +} + /** * Wrap `cloneElement` to abort if the passed element is not a valid element and apply * all vnode normalizations. @@ -215,6 +230,7 @@ export { Fragment, isValidElement, isFragment, + isMemo, findDOMNode, Component, PureComponent, @@ -263,6 +279,7 @@ export default { isValidElement, isElement, isFragment, + isMemo, findDOMNode, Component, PureComponent, diff --git a/compat/test/browser/isMemo.test.js b/compat/test/browser/isMemo.test.js new file mode 100644 index 0000000000..a11c1a9823 --- /dev/null +++ b/compat/test/browser/isMemo.test.js @@ -0,0 +1,37 @@ +import { createElement as preactCreateElement, Fragment } from 'preact'; +import React, { createElement, isMemo, memo } from 'preact/compat'; + +describe('isMemo', () => { + it('should check return false for invalid arguments', () => { + expect(isMemo(null)).to.equal(false); + expect(isMemo(false)).to.equal(false); + expect(isMemo(true)).to.equal(false); + expect(isMemo('foo')).to.equal(false); + expect(isMemo(123)).to.equal(false); + expect(isMemo([])).to.equal(false); + expect(isMemo({})).to.equal(false); + }); + + it('should detect a preact memo', () => { + function Foo() { + return

Hello World

; + } + let App = memo(Foo); + expect(isMemo(App)).to.equal(true); + }); + + it('should not detect a normal element', () => { + function Foo() { + return

Hello World

; + } + expect(isMemo(Foo)).to.equal(false); + }); + + it('should detect a preact vnode as false', () => { + expect(isMemo(preactCreateElement(Fragment, {}))).to.equal(false); + }); + + it('should detect a compat vnode as false', () => { + expect(isMemo(React.createElement(Fragment, {}))).to.equal(false); + }); +});