diff --git a/docs/PRINCIPLE.md b/docs/PRINCIPLE.md index 072fcc4..1a31ece 100644 --- a/docs/PRINCIPLE.md +++ b/docs/PRINCIPLE.md @@ -122,7 +122,7 @@ export class Button extends UUI.ClassComponent({ }, }) { render() { - const { Root, LoadingSpinner, Content } = this.nodes + const { Root, LoadingSpinner, Content } = this.state.nodes return ( { render() { - const { Root, LoadingSpinner, Content } = this.nodes + const { Root, LoadingSpinner, Content } = this.state.nodes return ( { constructor(props: ToasterFeatureProps) { super(props) + this.state.toasts = [] as IToast[] } - public state = { - toasts: [] as IToast[], - }; - static create(props: ToasterFeatureProps, container = ReactHelper.document?.body) { const containerElement = ReactHelper.document?.createElement("div"); if (!containerElement || !container) return null; @@ -111,7 +108,7 @@ export class Toaster extends UUI.ClassComponent({ } render() { - const { Root } = this.nodes + const { Root } = this.state.nodes return ( diff --git a/src/core/uui.tsx b/src/core/uui.tsx index 70690dc..7c1a269 100644 --- a/src/core/uui.tsx +++ b/src/core/uui.tsx @@ -7,7 +7,7 @@ */ -import React from 'react'; +import React, { useMemo } from 'react'; import { mapValues, pick, isString, omit, merge, clone, uniq, isEmpty, chain, mergeWith } from 'lodash'; import classNames from 'classnames'; import { mergeRefs } from '../utils/mergeRefs'; @@ -227,6 +227,8 @@ function ComponentNode

(Target return <_Target {...omit(_props, 'customize', 'ref')} + prefix={options.prefix} + separator={options.separator} ref={ref} className={_props.className} customize={finalCustomize} @@ -287,10 +289,14 @@ export type UUIConvenienceProps = { style?: React.CSSProperties; /** - * React native support data-* attributes type, + * React natively support data-* attributes type, * dont need to redeclare again convenience data attributes. */ } +export type UUIMetaProps = { + prefix?: string; + separator?: string; +} export type UUIComponentProps = P & UUIConvenienceProps & UUIComponentCustomizeProps export type UUIFunctionComponentProps any> = Parameters[0] export type UUIClassComponentProps> = React.ComponentProps @@ -335,14 +341,19 @@ export abstract class UUI { }, WrappedComponent: (props: P, nodes: UUIComponentNodes) => React.ReactElement, ) { - const finalOptions: Required = getFinalOptions(options) - const nodes = compileNodes(finalOptions) - const component: React.FunctionComponent

= (props) => { + const component: React.FunctionComponent

= (props) => { + const { prefix, separator } = props; + // eslint-disable-next-line react-hooks/rules-of-hooks + const { finalOptions, nodes } = useMemo(() => { + const finalOptions = getFinalOptions(options, { prefix, separator }) + const nodes = compileNodes(finalOptions) + return { finalOptions, nodes } + }, [prefix, separator]) const compiledProps = compileProps(props, finalOptions, undefined) - injectCustomizeProps(nodes, compiledProps) + injectCustomizeProps(nodes, compiledProps); return WrappedComponent(compiledProps, nodes) } - component.displayName = ` [Component] ${finalOptions.name}` + component.displayName = ` [Component] ${options.name}` return component } @@ -375,27 +386,40 @@ export abstract class UUI { nodes: X; }, ) { - const finalOptions: Required = getFinalOptions(options) - const nodes = compileNodes(finalOptions) - return class WrappedComponent

extends React.Component

{ - nodes: UUIComponentNodes - displayName: string + return class WrappedComponent

extends React.Component

}, SS> { + static displayName = ` [Component] ${options.name}` + state: S & { nodes: UUIComponentNodes } + + componentDidUpdate(prevProps: P & UUIConvenienceProps & UUIMetaProps & Z) { + if ( + prevProps.prefix !== this.props.prefix || + prevProps.separator !== this.props.separator + ) { + const finalOptions = getFinalOptions(options, this.props) + this.setState({ nodes: compileNodes(finalOptions) }) + const compiledProps = compileProps(this.props, finalOptions, (this.props as any).innerRef || undefined) + injectCustomizeProps(this.state.nodes, compiledProps) + } + } + constructor(props: P & UUIConvenienceProps & Z) { super(props) - this.displayName = ` [Component] ${finalOptions.name}` + const finalOptions = getFinalOptions(options, props) + this.state = { nodes: compileNodes(finalOptions) } as any + this.state.nodes = compileNodes(finalOptions) const compiledProps = compileProps(props, finalOptions, (props as any).innerRef || undefined) - injectCustomizeProps(nodes, compiledProps) - this.nodes = nodes + injectCustomizeProps(this.state.nodes, compiledProps) } } } } -function getFinalOptions(options: any) { +function getFinalOptions(options: any, props: any) { return { - ...options, - prefix: options.prefix || 'UUI', - separator: options.separator || '-', + nodes: options.nodes, + name: options.name, + prefix: props.prefix || options.prefix || 'UUI', + separator: props.separator || options.separator || '-', } } diff --git a/tests/core/__snapshots__/uui.test.tsx.snap b/tests/core/__snapshots__/uui.test.tsx.snap index 180d532..02c2c48 100644 --- a/tests/core/__snapshots__/uui.test.tsx.snap +++ b/tests/core/__snapshots__/uui.test.tsx.snap @@ -145,36 +145,6 @@ exports[`UUIComponent customize [convenience][className, style] 1`] = ` `; -exports[`UUIComponentHOC [more options] 1`] = ` -

-
-
-

- Lorem ipsum -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sagittis magna finibus lorem semper malesuada. Maecenas vel tristique odio. Duis non nisi turpis. Nam malesuada condimentum ultrices. Mauris lectus ante, sollicitudin in odio ac, elementum euismod ipsum. Nulla semper mattis erat, nec sollicitudin turpis gravida ut. Donec aliquet sit amet enim at consequat. Duis lacinia libero ipsum, in faucibus sapien egestas quis. Pellentesque pretium gravida elit sed viverra. Morbi vitae enim at quam cursus dapibus non ac erat. Phasellus ligula lectus, tempor ut ultricies nec, ornare cursus mauris. -

-

- Proin mollis, dui in volutpat consectetur, quam sapien eleifend eros, ut consectetur neque mauris vel velit. Aliquam sed ultrices ex. Nam sed augue ligula. Cras et enim lorem. Maecenas eget lacus diam. Praesent nec lectus ut ante suscipit semper. Vestibulum congue justo eu rutrum tempus. Maecenas imperdiet neque sapien, vitae pretium dui aliquam et. Suspendisse potenti. Curabitur euismod nisi a urna auctor, quis viverra tellus euismod. Nulla facilisi. Proin eleifend nunc a nisi venenatis scelerisque. Aliquam erat volutpat. Etiam finibus laoreet ipsum, a rutrum odio venenatis vel. -

-
-
-
-`; - exports[`UUIComponentHOC [no Root node] 1`] = `
`; +exports[`UUIComponentHOC [options] 1`] = ` +
+
+
+

+ Lorem ipsum +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sagittis magna finibus lorem semper malesuada. Maecenas vel tristique odio. Duis non nisi turpis. Nam malesuada condimentum ultrices. Mauris lectus ante, sollicitudin in odio ac, elementum euismod ipsum. Nulla semper mattis erat, nec sollicitudin turpis gravida ut. Donec aliquet sit amet enim at consequat. Duis lacinia libero ipsum, in faucibus sapien egestas quis. Pellentesque pretium gravida elit sed viverra. Morbi vitae enim at quam cursus dapibus non ac erat. Phasellus ligula lectus, tempor ut ultricies nec, ornare cursus mauris. +

+

+ Proin mollis, dui in volutpat consectetur, quam sapien eleifend eros, ut consectetur neque mauris vel velit. Aliquam sed ultrices ex. Nam sed augue ligula. Cras et enim lorem. Maecenas eget lacus diam. Praesent nec lectus ut ante suscipit semper. Vestibulum congue justo eu rutrum tempus. Maecenas imperdiet neque sapien, vitae pretium dui aliquam et. Suspendisse potenti. Curabitur euismod nisi a urna auctor, quis viverra tellus euismod. Nulla facilisi. Proin eleifend nunc a nisi venenatis scelerisque. Aliquam erat volutpat. Etiam finibus laoreet ipsum, a rutrum odio venenatis vel. +

+
+
+
+`; + +exports[`UUIComponentHOC [options] 2`] = ` +
+
+
+

+ Lorem ipsum +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sagittis magna finibus lorem semper malesuada. Maecenas vel tristique odio. Duis non nisi turpis. Nam malesuada condimentum ultrices. Mauris lectus ante, sollicitudin in odio ac, elementum euismod ipsum. Nulla semper mattis erat, nec sollicitudin turpis gravida ut. Donec aliquet sit amet enim at consequat. Duis lacinia libero ipsum, in faucibus sapien egestas quis. Pellentesque pretium gravida elit sed viverra. Morbi vitae enim at quam cursus dapibus non ac erat. Phasellus ligula lectus, tempor ut ultricies nec, ornare cursus mauris. +

+

+ Proin mollis, dui in volutpat consectetur, quam sapien eleifend eros, ut consectetur neque mauris vel velit. Aliquam sed ultrices ex. Nam sed augue ligula. Cras et enim lorem. Maecenas eget lacus diam. Praesent nec lectus ut ante suscipit semper. Vestibulum congue justo eu rutrum tempus. Maecenas imperdiet neque sapien, vitae pretium dui aliquam et. Suspendisse potenti. Curabitur euismod nisi a urna auctor, quis viverra tellus euismod. Nulla facilisi. Proin eleifend nunc a nisi venenatis scelerisque. Aliquam erat volutpat. Etiam finibus laoreet ipsum, a rutrum odio venenatis vel. +

+
+
+
+`; + +exports[`UUIComponentHOC [options] 3`] = ` +
+ TestZComponent +
+`; + exports[`UUIComponentHOC - customize 1`] = `
{ } }) { render() { - const { Root, Container, Article, Title, Paragraph } = this.nodes + const { Root, Container, Article, Title, Paragraph } = this.state.nodes return ( @@ -140,7 +140,7 @@ it('UUIComponentHOC - customize', () => { } }) { render() { - const { Root, X } = this.nodes + const { Root, X } = this.state.nodes return ( @@ -237,9 +237,9 @@ it('UUIComponentHOC - customize', () => { expect(tree).toMatchSnapshot(); }); -it('UUIComponentHOC [more options]', () => { +it('UUIComponentHOC [options]', () => { const XUITestFunctionComponent = UUI.FunctionComponent({ - name: 'XUITestFunctionComponent', + name: 'TestXComponent', prefix: 'XUI', separator: '=', nodes: { @@ -264,11 +264,65 @@ it('UUIComponentHOC [more options]', () => { ) }) + class UUITestClassComponent extends UUI.ClassComponent({ + prefix: 'ZUI', + name: 'TestZComponent', + separator: '+', + nodes: { + Root: 'div', + } + }) { + render() { + const { Root } = this.state.nodes + return ( + + TestZComponent + + ) + } + } + const tree1 = renderer .create() .toJSON(); expect(tree1).toMatchSnapshot(); + + const tree2 = renderer + .create() + .toJSON(); + + expect(tree2).toMatchSnapshot(); + + const tree3 = renderer + .create() + .toJSON(); + + expect(tree3).toMatchSnapshot(); + + const wrapper1 = shallow() as any; + const before1 = wrapper1.html() + wrapper1.setProps({ prefix: 'HUI', name: 'ChangedTestHComponent', separator: '~' }); + const after1 = wrapper1.html() + expect(before1).not.toEqual(after1); + + const wrapper2 = shallow() as any; + const before2 = wrapper2.html() + wrapper2.setProps({ prefix: 'CUI', name: 'ChangedTestCComponent', separator: '~' }); + const after2 = wrapper2.html() + expect(before2).not.toEqual(after2); }); it('UUIComponentHOC [no Root node]', () => {