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

18.5k Views Asked by At

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?

4

There are 4 best solutions below

1
On BEST ANSWER

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:

0
On

in my case i solved this error to do that //make const

const MemoizedRenderHtml = React.memo(RenderHtml);

then call it in return

<MemoizedRenderHtml
    contentWidth={width}
    baseStyle={renderHtmlTagsStyles}
     source={{ html: textToDisplay }}
      />

try this you can get rid of this error simple

0
On

I hit this issue with iOS home button/gesture - leaving the warning is the right thing to do. This happens because iOS changes btwn light/dark mode when leaving the app in order to grab screenshots: https://github.com/facebook/react-native/issues/28525

0
On

Removing this prop, enableExperimentalBRCollapsing={true}, from <RenderHtml /> worked for me.