|
| 1 | +# Ottrelite Instrumentation for React |
| 2 | + |
| 3 | +Ottrelite Instrumentation for React provides two main ways to add performance tracing to your React applications: |
| 4 | + |
| 5 | +1. **React Hooks API** - Manual instrumentation using React hooks like `useComponentRenderTracing` |
| 6 | +2. **Babel Plugin** - Automatic instrumentation using the `"use trace"` directive |
| 7 | + |
| 8 | +:::note |
| 9 | +If you're using a recent version of React Native, consider using [React Performance Tracks](/docs/introduction#react-native-devtools) instead. |
| 10 | + |
| 11 | +React Performance Tracks offer significantly more features compared to Ottrelite's basic render time tracking, including: |
| 12 | + |
| 13 | +- **Scheduler tracking** with 4 priority levels (Blocking, Transition, Suspense, Idle) |
| 14 | +- **Detailed render phases** (Update, Render, Commit, Effects) with flamegraph visualization |
| 15 | +- **Cascading update detection** to identify performance regressions |
| 16 | +- **Component-level insights** with render and effect durations |
| 17 | +- **Props change inspection** to identify unnecessary renders |
| 18 | +- **Integration with browser DevTools** alongside network requests, JavaScript execution, and event loop activity |
| 19 | + |
| 20 | +Ottrelite serves as an alternative solution for those who haven't upgraded to the latest React Native version yet. |
| 21 | +::: |
| 22 | + |
| 23 | +## Installation |
| 24 | + |
| 25 | +First, ensure you are already using `@ottrelite/core`. Then, add this package: |
| 26 | + |
| 27 | +```bash |
| 28 | +npm install @ottrelite/instrumentation-react |
| 29 | +``` |
| 30 | + |
| 31 | +## Features |
| 32 | + |
| 33 | +### React Hooks API |
| 34 | + |
| 35 | +The React API provides hooks that allow you to manually instrument your components. The main hook is `useComponentRenderTracing`, which traces a component's render lifecycle. |
| 36 | + |
| 37 | +#### `useComponentRenderTracing` |
| 38 | + |
| 39 | +This hook traces a component's render lifecycle (i.e., any - first or subsequent - render). |
| 40 | + |
| 41 | +```typescript |
| 42 | +function MyComponent() { |
| 43 | + const { markJSRenderEnd } = useComponentRenderTracing("MyComponent"); |
| 44 | + |
| 45 | + // Your component logic here... |
| 46 | + |
| 47 | + const contents = <View>{/* ... */}</View>; |
| 48 | + |
| 49 | + markJSRenderEnd(); |
| 50 | + |
| 51 | + return contents; |
| 52 | +} |
| 53 | +``` |
| 54 | + |
| 55 | +The hook **must** be called first in the component to mark the JS logic start time undelayed. Any components you render & return **must** first be stored in a variable, and returned **after** calling the hook's returned `markJSRenderEnd` function. |
| 56 | + |
| 57 | +:::important |
| 58 | +The traced span starts after the JS render logic finishes and ends after the component is rendered to the tree. Additionally, the JS render execution time is measured and reported as a `jsLogicDuration` attribute within the span. |
| 59 | +::: |
| 60 | + |
| 61 | +### Babel Plugin for Automatic Instrumentation |
| 62 | + |
| 63 | +The Babel plugin automatically instruments React components and hooks using the `"use trace"` directive. This provides a convenient way to add tracing without manually calling hooks. |
| 64 | + |
| 65 | +#### Setting up the Babel Plugin |
| 66 | + |
| 67 | +Add the plugin to your Babel configuration. If you are using a `babel.config.js` file: |
| 68 | + |
| 69 | +```javascript |
| 70 | +module.exports = { |
| 71 | + ..., |
| 72 | + plugins: ['module:@ottrelite/instrumentation-react'], |
| 73 | + ... |
| 74 | +}; |
| 75 | +``` |
| 76 | + |
| 77 | +#### Using the "use trace" Directive |
| 78 | + |
| 79 | +Add the `"use trace"` directive as the first line in functions you want to instrument: |
| 80 | + |
| 81 | +- **Function components**: First line of the component function body |
| 82 | +- **Class components**: First line of the `render` method body |
| 83 | +- **Hooks**: First line of the hook function body |
| 84 | + |
| 85 | +The directive schema is `"use trace [API] [Name]"` where: |
| 86 | + |
| 87 | +- `[API]` (default: `dev`) - Either `dev` (Ottrelite Development API) or `otel` (OpenTelemetry JS SDK) |
| 88 | +- `[Name]` (optional) - Custom name for the tracer; if not provided, the function/component name is used |
| 89 | + |
| 90 | +#### Functions |
| 91 | + |
| 92 | +```typescript |
| 93 | +export function MyFunction() { |
| 94 | + "use trace"; |
| 95 | + // Your logic here |
| 96 | +}); |
| 97 | +``` |
| 98 | + |
| 99 | +#### Function Components |
| 100 | + |
| 101 | +```typescript |
| 102 | +import { memo } from 'react'; |
| 103 | + |
| 104 | +export const MyComponent = memo(function MyComponent() { |
| 105 | + "use trace"; |
| 106 | + |
| 107 | + // Your component logic here |
| 108 | + |
| 109 | + return <div>Hello World</div>; |
| 110 | +}); |
| 111 | +``` |
| 112 | + |
| 113 | +:::note |
| 114 | +As seen in the examples above, the components are wrapped in `memo` to prevent unnecessary re-renders. This is a common practice in React to optimize performance, but it is not compulsory - the instrumentation would still work without it. However, in such case, it would be expected that most likely the instrumentation would produce many entries, for invocations of the component which didn't actually result in an update to the shadow tree. |
| 115 | +::: |
| 116 | + |
| 117 | +When using `memo()`, you must either: |
| 118 | +1. Use a named function: `memo(function MyComponent() { ... })` |
| 119 | +2. Provide an explicit name: `"use trace dev MyComponent"` |
| 120 | + |
| 121 | +:::important |
| 122 | +Anonymous functions without explicit names will result in an error. |
| 123 | +::: |
| 124 | + |
| 125 | +#### Class Components |
| 126 | + |
| 127 | +```typescript |
| 128 | +import { Component } from 'react'; |
| 129 | + |
| 130 | +export class MyClassComponent extends Component { |
| 131 | + render() { |
| 132 | + 'use trace'; |
| 133 | + |
| 134 | + // Your render logic here |
| 135 | + |
| 136 | + return <div>Hello World</div>; |
| 137 | + } |
| 138 | + |
| 139 | + shouldComponentUpdate(nextProps, nextState, nextContext) { |
| 140 | + // only rerender if props, state, or context have changed |
| 141 | + return ( |
| 142 | + !_.isEqual(this.props, nextProps) || |
| 143 | + !_.isEqual(this.state, nextState) || |
| 144 | + !_.isEqual(this.context, nextContext) |
| 145 | + ); |
| 146 | + } |
| 147 | +} |
| 148 | +``` |
| 149 | + |
| 150 | +:::note |
| 151 | +As seen in the example above, the component includes an explicit implementation of `shouldComponentUpdate` to prevent unnecessary re-renders. This is a common practice in React to optimize performance, but it is not compulsory - the instrumentation would still work without it. However, in such case, it would be expected that most likely the instrumentation would produce many entries, for invocations of the component which didn't actually result in an update to the shadow tree. |
| 152 | +::: |
| 153 | + |
| 154 | +#### Tracer Name Resolution |
| 155 | + |
| 156 | +The tracer name is determined in the following priority order: |
| 157 | + |
| 158 | +| Declaration style / context | Name | |
| 159 | +| --------------------------------------------------- | ---------------------------------------- | |
| 160 | +| Explicit name from the string after `"use trace"` | The passed string | |
| 161 | +| component with `displayName` or `name` | `displayName` or `name` property | |
| 162 | +| named function/class | Name of the function/class | |
| 163 | +| arrow function | Name of the assignment target identifier | |
| 164 | +| anonymous function/class | Name of the assignment target identifier | |
| 165 | + |
| 166 | +Both `[API]` and `[Name]` parameters are optional. The shortest form is `"use trace"` which is equivalent to `"use trace dev <resolved component name>"`. |
| 167 | + |
| 168 | +:::tip |
| 169 | +If you pass only one parameter, it will be treated as `[API]` first; if the value doesn't match the API options, it will be treated as the `[Name]` parameter. |
| 170 | +::: |
| 171 | + |
| 172 | +:::tip |
| 173 | +Components wrapped in `memo()` or class components with `shouldComponentUpdate` are recommended to prevent unnecessary re-renders and reduce noise in tracing data. |
| 174 | +::: |
0 commit comments