Achieving 60 FPS in React Native with Imperative Manipulation
Article Summary
Sourav Yadav from Razorpay discovered that React Native's declarative approach was causing frame rates to plummet to 0 FPS. His solution? Break the rules and go imperative.
React Native's declarative API is convenient, but it comes with a hidden cost: the reconciliation algorithm can become a performance bottleneck when dealing with thousands of nodes. Razorpay's engineering team tested both declarative and imperative approaches to find where the breaking point occurs and how to fix it.
Key Takeaways
- With 3000 animated nodes, declarative updates dropped to 0 FPS on JS thread
- Imperative manipulation using setNativeProps bypasses React's reconciliation overhead entirely
- UI thread maintained 60 FPS imperatively vs dropping to 38 FPS declaratively
- Trade-off: imperative nodes stay in memory even when hidden, increasing consumption
When React Native apps hit performance walls with complex UIs, setNativeProps can maintain 60 FPS by skipping reconciliation, but only use it as a last resort after exhausting declarative optimizations.
About This Article
React's diffing algorithm runs in O(n) complexity, which caused performance issues when rendering large component trees. Adding a TextInput to 3000 animated nodes dropped the JS thread to 7 FPS and the UI thread to 38 FPS during state updates.
Sourav Yadav's team used setNativeProps to directly manipulate native view properties. This bypassed React's reconciliation process and gave them imperative control over component visibility and attributes.
Imperative manipulation kept both the JS and UI threads at a consistent 60 FPS with 3000 nodes. The frame drops that happened with the declarative approach disappeared, and TextInput operations ran without lag.