From 254cf3360981ea9132f1a0a5a9edb0b713df4b08 Mon Sep 17 00:00:00 2001 From: Anton Timmermans Date: Wed, 21 Nov 2018 10:16:26 +0100 Subject: [PATCH] Fix RichText rerendering when it shouldn't The prepareEditableTree and onChangeEditableValue function stacks would have a new reference on every render. This prevents that by memoized the stack based on the previous stack and the newly added function. --- packages/annotations/src/format/annotation.js | 55 ++++++++++++++----- packages/annotations/src/store/selectors.js | 6 +- .../rich-text/src/register-format-type.js | 25 ++++++--- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/packages/annotations/src/format/annotation.js b/packages/annotations/src/format/annotation.js index a2f6f2973c7268..e72786bad0e3bc 100644 --- a/packages/annotations/src/format/annotation.js +++ b/packages/annotations/src/format/annotation.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import memize from 'memize'; + /** * WordPress dependencies */ @@ -115,6 +120,40 @@ function updateAnnotationsWithPositions( annotations, positions, { removeAnnotat } ); } +/** + * Create prepareEditableTree memoized based on the annotation props. + * + * @param {Object} The props with annotations in them. + * + * @return {Function} The prepareEditableTree. + */ +const createPrepareEditableTree = memize( ( props ) => { + const { annotations } = props; + + return ( formats, text ) => { + if ( annotations.length === 0 ) { + return formats; + } + + let record = { formats, text }; + record = applyAnnotations( record, annotations ); + return record.formats; + }; +} ); + +/** + * Returns the annotations as a props object. Memoized to prevent re-renders. + * + * @param {Array} The annotations to put in the object. + * + * @return {Object} The annotations props object. + */ +const getAnnotationObject = memize( ( annotations ) => { + return { + annotations, + }; +} ); + export const annotation = { name: FORMAT_NAME, title: __( 'Annotation' ), @@ -128,21 +167,9 @@ export const annotation = { return null; }, __experimentalGetPropsForEditableTreePreparation( select, { richTextIdentifier, blockClientId } ) { - return { - annotations: select( STORE_KEY ).__experimentalGetAnnotationsForRichText( blockClientId, richTextIdentifier ), - }; - }, - __experimentalCreatePrepareEditableTree( { annotations } ) { - return ( formats, text ) => { - if ( annotations.length === 0 ) { - return formats; - } - - let record = { formats, text }; - record = applyAnnotations( record, annotations ); - return record.formats; - }; + return getAnnotationObject( select( STORE_KEY ).__experimentalGetAnnotationsForRichText( blockClientId, richTextIdentifier ) ); }, + __experimentalCreatePrepareEditableTree: createPrepareEditableTree, __experimentalGetPropsForEditableTreeChangeHandler( dispatch ) { return { removeAnnotation: dispatch( STORE_KEY ).__experimentalRemoveAnnotation, diff --git a/packages/annotations/src/store/selectors.js b/packages/annotations/src/store/selectors.js index ca6fcb64796d5f..f161b94a79fea7 100644 --- a/packages/annotations/src/store/selectors.js +++ b/packages/annotations/src/store/selectors.js @@ -4,6 +4,8 @@ import createSelector from 'rememo'; import { get, flatMap } from 'lodash'; +const emptyArray = []; + /** * Returns the annotations for a specific client ID. * @@ -19,7 +21,7 @@ export const __experimentalGetAnnotationsForBlock = createSelector( } ); }, ( state, blockClientId ) => [ - get( state, blockClientId, [] ), + get( state, blockClientId, emptyArray ), ] ); @@ -54,7 +56,7 @@ export const __experimentalGetAnnotationsForRichText = createSelector( } ); }, ( state, blockClientId ) => [ - get( state, blockClientId, [] ), + get( state, blockClientId, emptyArray ), ] ); diff --git a/packages/rich-text/src/register-format-type.js b/packages/rich-text/src/register-format-type.js index 8b61fc3a4308b1..1fd7206ad5892c 100644 --- a/packages/rich-text/src/register-format-type.js +++ b/packages/rich-text/src/register-format-type.js @@ -2,6 +2,7 @@ * External dependencies */ import { mapKeys } from 'lodash'; +import memize from 'memize'; /** * WordPress dependencies @@ -119,6 +120,14 @@ export function registerFormatType( name, settings ) { dispatch( 'core/rich-text' ).addFormatTypes( settings ); + const emptyArray = []; + const getFunctionStackMemoized = memize( ( previousStack = emptyArray, newFunction ) => { + return [ + ...previousStack, + newFunction, + ]; + } ); + if ( settings.__experimentalGetPropsForEditableTreePreparation ) { @@ -133,13 +142,13 @@ export function registerFormatType( name, settings ) { const additionalProps = {}; if ( settings.__experimentalCreatePrepareEditableTree ) { - additionalProps.prepareEditableTree = [ - ...( props.prepareEditableTree || [] ), + additionalProps.prepareEditableTree = getFunctionStackMemoized( + props.prepareEditableTree, settings.__experimentalCreatePrepareEditableTree( props[ `format_${ name }` ], { richTextIdentifier: props.identifier, blockClientId: props.clientId, - } ), - ]; + } ) + ); } if ( settings.__experimentalCreateOnChangeEditableValue ) { @@ -155,16 +164,16 @@ export function registerFormatType( name, settings ) { return accumulator; }, {} ); - additionalProps.onChangeEditableValue = [ - ...( props.onChangeEditableValue || [] ), + additionalProps.onChangeEditableValue = getFunctionStackMemoized( + props.onChangeEditableValue, settings.__experimentalCreateOnChangeEditableValue( { ...props[ `format_${ name }` ], ...dispatchProps, }, { richTextIdentifier: props.identifier, blockClientId: props.clientId, - } ), - ]; + } ) + ); } return