Search code examples
react-nativereact-native-render-html

react-native-render-html: "You seem to update the X prop of the Y component in short periods of time..."


Notes: I'm the author of react-native-render-html. This question is for educational purposes, in compliance with StackOverflow policy.

I am rendering RenderHtml component in a WebDisplay component like so:

import * as React from 'react';
import {ScrollView, StyleSheet, Text, useWindowDimensions} from 'react-native';
import RenderHtml from 'react-native-render-html';

const html = '<div>Hello world!</div>';

function WebDisplay({html}) {
  const {width: contentWidth} = useWindowDimensions();
  const tagsStyles = {
    a: {
      textDecorationLine: 'none',
    },
  };
  return (
    <RenderHtml
      contentWidth={contentWidth}
      source={{html}}
      tagsStyles={tagsStyles}
    />
  );
}

export default function App() {
  const [isToastVisible, setIsToastVisible] = React.useState(false);
  React.useEffect(function flipToast() {
    const timeout = setTimeout(() => {
      setIsToastVisible((v) => !v);
    }, 30);
    return () => {
      clearTimeout(timeout);
    };
  });
  return (
    <ScrollView contentContainerStyle={styles.container}>
      <WebDisplay html={html} />
      {isToastVisible && <Text>This is a toast!</Text>}
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flexGrow: 1,
  },
});

Why am I getting this warning, what does it mean and how to fix it?


Solution

  • Usually, this warning shows up when:

    • The parent (currently App) component updates very often and causes WebDisplay component to re-render. In the provided snippet, every 30 milliseconds;
    • At least one prop passed to RenderHTML is referentially unstable between each re-render. In the provided snippet, tagsStyles reference changes on every re-render.

    Notice that between every update of the App component caused by the useEffect hook, the html prop passed to WebDisplay is unchanged. But WebDisplay is re-rendered anyway because it is not "pure".

    For this very reason, a pretty straightforward solution is to wrap WebDisplay in React.memo:

    const WebDisplay = React.memo(function WebDisplay({html}) {
      const {width: contentWidth} = useWindowDimensions();
      const tagsStyles = {
        a: {
          textDecorationLine: 'none',
        },
      };
      return (
        <RenderHtml
          contentWidth={contentWidth}
          source={{html}}
          tagsStyles={tagsStyles}
        />
      );
    });
    

    You can learn more about this technique in the official documentation.

    Note: "pure" terminology comes from React PureComponent class. I think React.pure would have been less ambiguous than React.memo because we now use "memoization" to designate optimization techniques applying to both components and props, which can be confusing. I prefer to preserve "pure" terminology for components, and "memoization" for props.

    Another solution is to move tagsStyles outside the WebDisplay function body. In that case, it will be instantiated only once and become referentially stable. Because RenderHtml itself is pure, it won't re-render its own subcomponents and the warning should disappear:

    const tagsStyles = {
      a: {
        textDecorationLine: 'none',
      },
    };
    
    function WebDisplay({html}) {
      const {width: contentWidth} = useWindowDimensions();
      return (
        <RenderHtml
          contentWidth={contentWidth}
          source={{html}}
          tagsStyles={tagsStyles}
        />
      );
    };
    

    Note: It's a great habit to move any props which does not depend on any other state or prop outside of the body of a functional component.

    Finally, if your use case involves tagsStyles depending on a prop, you could memoize it with React.useMemo:

    function WebDisplay({html, anchorColor}) {
      const {width: contentWidth} = useWindowDimensions();
      const tagsStyles = React.useMemo(
        () => ({
          a: {
            color: anchorColor,
            textDecorationLine: 'none',
          },
        }),
        [anchorColor],
      );
      return (
        <RenderHtml
          contentWidth={contentWidth}
          source={{html}}
          tagsStyles={tagsStyles}
        />
      );
    }
    

    More information on this hook in the official documentation.

    If you still don't have a clear mental model of how component updates work in React, I suggest those readings to enhance your skills: