Improving your React Native application performance with react-navigation-focus-render
Kyle Johnson
Posted on February 20, 2022
Performance in React Native has always been a bit of a battle, great performance is achievable but is a lot more sensitive to unoptimized code than traditional web development.
Background
I'd recently discovered an issue in my application where my home tab screen had a collection of components that contained multiple re-renders.
The problem was quite straightforward to resolve, but during this, I found out that this non-performant screen was slowing down the other tabs within my application.
A simple example of why this happened
Let's make a simple example that replicates this issue.
ExpensiveComponent
Here's our component that's causing the issue, every time this renders we'll see an obvious performance hit. It's connected to redux and will re-render whenever the state changes for the count.
const ExpensiveComponent = () => {
const {count} = useSelector((state) => ({
count: state.count,
}));
return (
<>
{!!count && <Text>Count is {count}</Text>}
{new Array(5000).fill(0).map((v, k) => (
<Text key={k}>{v}</Text>
))}
</>
);
};
HomeScreen
Our home screen renders the expensive component and lets us go to screen 2. As long as ExpensiveComponent is re-rendering we will see an obvious performance hit.
const HomeScreen = () => {
const navigation = useNavigation();
const goScreen2 = ()=>{
navigation.navigate('Screen2')
}
return (
<>
<Button
title={'Go to Screen 2'}
onPress={goScreen2}
/>
<ExpensiveComponent />
</>
);
};
Screen2
Our second screen has no performance issues by itself, it contains a button that dispatches an action to update the count. It doesn't render much and you'd expect pressing the button and displaying an updated count to be immediate.
const Screen2: React.FC<ComponentType> = ({}) => {
const {count} = useSelector((state) => ({
count: state.count,
}));
const dispatch = useDispatch();
const setCount = useCallback(
(data: number) => {
return dispatch(AppActions.setCount(data));
},
[dispatch],
);
const onPress = ()=> {
setCount((count || 0) + 1)
}
return (
<Button
onPress={onPress}
title={`Update Count (${count || 0})`}/>
);
};
You would expect Screen2 to have no performance issues right? Wrong. Pressing the update count button was consistently blocking the UI by around 250ms, this can be seen by using a tool I made, react-native-performance-monitor.
So why did this happen?
The reason made sense, other tabs were using updating the state that was also used on the HomeTab and as it turned out, inactive tabs will re-render even if they aren't shown.
Even with optimized components, the fact that this happens is useful to be aware of. If you have 5 tabs in your app that have been visited in a session, any state updates can trigger re-renders on all of them.
Preventing this behavior with react-navigation-focus-render
This is where my new npm package comes in. By wrapping the render our ExpensiveComponent in <FocusRender, children are not re-rendered until the screen is focused
const ExpensiveComponent = () => {
const {count} = useSelector((state) => ({
count: state.count,
}));
return (
**<FocusRender>**
{!!count && <Text>Count is {count}</Text>}
{new Array(5000).fill(0).map((v, k) => (
<Text key={k}>{v}</Text>
))}
**</FocusRender>**
);
};
Simply by adding this, our example is more performant. Here's the comparison to prove it:
This shows an average render time of around 6ms vs the original 250ms.
How does this work?
It's quite simple, This module works by preventing screen re-renders of inactive screens until that screen is focused.
The entire code can be found here. It uses the useIsFocused() hook provided by react-navigation combined with a classic shouldComponentUpdate, returning true only if the screen is focused.
To illustrate this clearly, here's how the above example behaves.
As you can see, it's noticeable when this re-render occurs due to how expensive the component is. However, in cases less extreme, it's more likely to behave like the following:
Conclusion
Although this library should not replace optimizing components, I believe with big applications this library can introduce big performance benefits.
Let me know if you find this useful, you can check it out on GitHub :)
Happy Hacking!
Posted on February 20, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
February 20, 2022