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?
Usually, this warning shows up when:
App
) component updates very often and causes WebDisplay
component to re-render. In the provided snippet, every 30 milliseconds;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 thanReact.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: