diff --git a/src/Immutable.tsx b/src/Immutable.tsx index 0a76d38..ec08876 100644 --- a/src/Immutable.tsx +++ b/src/Immutable.tsx @@ -3,6 +3,11 @@ import * as React from 'react'; const ImmutableContext = React.createContext(0); +export type CompareProps> = ( + prevProps: Readonly>, + nextProps: Readonly>, +) => boolean; + /** * Get render update mark by `makeImmutable` root. * Do not deps on the return value as render times @@ -16,14 +21,30 @@ export function useImmutableMark() { * Wrapped Component will be marked as Immutable. * When Component parent trigger render, * it will notice children component (use with `responseImmutable`) node that parent has updated. + + * @param Component Passed Component + * @param triggerRender Customize trigger `responseImmutable` children re-render logic. Default will always trigger re-render when this component re-render. */ -export function makeImmutable>(Component: T): T { +export function makeImmutable>( + Component: T, + shouldTriggerRender?: CompareProps, +): T { const refAble = supportRef(Component); const ImmutableComponent = function (props: any, ref: any) { const refProps = refAble ? { ref } : {}; const renderTimesRef = React.useRef(0); - renderTimesRef.current += 1; + const prevProps = React.useRef(props); + + if ( + // Always trigger re-render if not provide `notTriggerRender` + !shouldTriggerRender || + shouldTriggerRender(prevProps.current, props) + ) { + renderTimesRef.current += 1; + } + + prevProps.current = props; return ( @@ -45,10 +66,7 @@ export function makeImmutable>(Component: T): */ export function responseImmutable>( Component: T, - propsAreEqual?: ( - prevProps: Readonly>, - nextProps: Readonly>, - ) => boolean, + propsAreEqual?: CompareProps, ): T { const refAble = supportRef(Component); diff --git a/tests/common.tsx b/tests/common.tsx index 1bae1bb..3e52b1d 100644 --- a/tests/common.tsx +++ b/tests/common.tsx @@ -22,7 +22,7 @@ export function Value({ id, value }: { id?: string; value: any }) { return (
- {str.replace(/^"/, '').replace(/"$/, '')} + {(str || '').replace(/^"/, '').replace(/"$/, '')}
); } diff --git a/tests/immutable.test.tsx b/tests/immutable.test.tsx index 9b162b9..00d76ba 100644 --- a/tests/immutable.test.tsx +++ b/tests/immutable.test.tsx @@ -73,6 +73,53 @@ describe('Immutable', () => { expect(container.querySelector('#raw')!.textContent).toEqual('3'); expect(container.querySelector('#value')!.textContent).toEqual('1'); }); + + it('customize re-render logic', () => { + const MyRoot = ({ + children, + }: { + children?: React.ReactNode; + trigger?: string; + notTrigger?: string; + }) => { + return ( + <> + + {children} + + ); + }; + + const ImmutableMyRoot = makeImmutable(MyRoot, (prev, next) => { + return prev.trigger !== next.trigger; + }); + + const { container, rerender } = render( + + + , + ); + expect(container.querySelector('#root').textContent).toEqual('1'); + expect(container.querySelector('#raw').textContent).toEqual('1'); + + // Update `notTrigger`: No Update + rerender( + + + , + ); + expect(container.querySelector('#root').textContent).toEqual('2'); + expect(container.querySelector('#raw').textContent).toEqual('1'); + + // Update `trigger`: Full Update + rerender( + + + , + ); + expect(container.querySelector('#root').textContent).toEqual('3'); + expect(container.querySelector('#raw').textContent).toEqual('2'); + }); }); it('ref-able', () => {