Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/oblosys/react-hook-tracer

See the order of hook-function calls in an interactive log, and inspect a function-component's props, state & refs inside its rendering.
https://github.com/oblosys/react-hook-tracer

debugging devtool hooks logging react tracing

Last synced: about 2 months ago
JSON representation

See the order of hook-function calls in an interactive log, and inspect a function-component's props, state & refs inside its rendering.

Awesome Lists containing this project

README

        

# React-hook-tracer [![Npm version](https://img.shields.io/npm/v/react-hook-tracer.svg?style=flat)](https://www.npmjs.com/package/react-hook-tracer) [![Build status](https://img.shields.io/github/actions/workflow/status/Oblosys/react-hook-tracer/build-test.yml?branch=main)](https://github.com/Oblosys/react-hook-tracer/actions/workflows/build-test.yml?query=branch/main)

The [react-hook-tracer package](https://www.npmjs.com/package/react-hook-tracer) traces function components to reveal the order of hook-function calls and lifecycle events in an interactive trace-log component. It also provides a live view of a component's props, state, and refs directly inside its renderering. The functionality is similar to what [react-lifecycle-visualizer](https://github.com/Oblosys/react-lifecycle-visualizer#readme) does for class components.

The demo below shows a traced `UserList` component that uses an effect to load two `User` components, which each have local state to keep track of button clicks. Newly added users get an index that is kept in the `newUserId` ref. The purple panels in the components and the trace log on the right-hand side are created by the package.



User-list demo screen capture

To trace a function component, simply import the hooks from `'react-hook-tracer'` instead of `'react'`, and call `useTracer()` at the start of the function. The `useTracer` hook returns a `TracePanel` component that can be included in the rendering to show the component's hooks, as well as the current values for its state, props, and refs. A global `TraceLog` component will show the trace messages, and when hovered over will highlight the traced hook in the corresponding `TracePanel`. The package currently supports tracing for `useCallback`, `useContext`, `useEffect`, `useInsertionEffect`, `useLayoutEffect`, `useMemo`, `useReducer`, `useRef`, and `useState`.

Note that even though tracing is disabled on production builds, it is not advisable to use react-hook-tracer on production.

### Demo

The demo above is live on a [CodeSandbox playground](https://codesandbox.io/s/github/Oblosys/react-hook-tracer/tree/demo/apps/react-hook-tracer-demo?file=/src/demos/Demo.tsx), and can be run locally with:

```sh
> git clone [email protected]:Oblosys/react-hook-tracer
> cd react-hook-tracer
> yarn install
> yarn build-lib
> yarn start
```

### Setup

Follow these steps to add tracing to a project.

#### Installation

Install the package with npm (or yarn):

```sh
> npm install react-hook-tracer
```

#### Include `TraceLog` component

The optional `TraceLog` component can be included anywhere in the application, but it probably makes the most sense to keep it near the root.

```tsx
import { TraceLog } from 'react-hook-tracer'
..
export const App = (): JSX.Element => (





)
```

If the `TraceLog` is omitted, traces will get logged to the console instead (see [Tracing to the browser console](#tracing-to-the-browser-console)).

#### Tracing a component

To illustrate how to trace a component, consider this simple `Counter` component:

```tsx
import { useState } from 'react'

const Counter = ({ title }: { title: string }) => {
const [n, setN] = useState(0)
return (


{title}

Value of n: {n}
setN((prev) => prev + 1)} />


)
}
```

Rendering the component with `` yields:


Counter component

To trace this component, import any hook functions (here only `useState`) from `'react-hook-tracer'`, together with the `useTracer` hook, and insert `const { TracePanel } = useTracer()` at the start of the component function (or at least before any traced hook calls). Traced hooks accept an optional argument to show a custom label, so as an example we pass `{ label: 'n' }` to `useState` here. The `TracePanel` component returned by `useTracer` is included in the rendering:

```tsx
import { useState, useTracer } from 'react-hook-tracer' // Update import

const Counter = ({ title }: { title: string }) => {
const { TracePanel } = useTracer() // Call useTracer at the start
const [n, setN] = useState(0, { label: 'n' }) // Add custom label (optional)
return (


{title}

Value of n: {n}
setN((prev) => prev + 1)} />

{/* Include TracePanel in rendering */}

)
}
```

Now the rendering of `` together with the trace log will look like this:


Traced Counter component

To experiment with this example, open the [CodeSandbox playground at `/src/demos/Counter.tsx`](https://codesandbox.io/s/github/Oblosys/react-hook-tracer/tree/demo/apps/react-hook-tracer-demo?file=/src/demos/Counter.tsx) and select 'Counter' instead of 'Demo' in the running app.

Note that traces are generated only by hooks imported from `'react-hook-tracer'`, and only in components that start with a `useTracer` call. Regular React hook calls following `useTracer` call do not generate traces, and neither do traced-hook calls in components without a `useTracer` call.

Besides `TracePanel`, `useTracer` also returns a function `trace: (message: string) => void`, which can be used to log custom trace messages.

#### Alternative import

Instead of using a named import, `'react-hook-tracer'` can also be imported as a variable, e.g. `traced`. Hooks can then be traced by prefixing each one with `traced.`:

```tsx
import { useTracer } from 'react-hook-tracer'
import * as traced from 'react-hook-tracer'

const Counter = ({ title }: { title: string }) => {
const { TracePanel } = useTracer()
const [n, setN] = traced.useState(0, { label: 'n' })
return (
..
)
}
```

#### React strict mode

You may want to temporarily disable [React strict mode](https://reactjs.org/docs/strict-mode.html) by removing the `` tags (typically in the root `index.tsx` or `index.jsx` file). In development builds, strict mode executes each component render twice, and also mounts components twice, which makes the log harder to read.

### Tracing to the browser console

To enable tracing to the browser console, leave out the `TraceLog` component, or call `setTracerConfig` anywhere in your project:

```ts
setTracerConfig({ traceToConsole: true })
```

Instead of a string representation, console traces show the actual object values for props, state, and refs, which means they can be expanded to inspect properties:


Console traces

Console traces may also be useful to diagnose infinite render loops, since the trace log will not update in that case as it is itself a React component. To see what the console traces look like, check out the [CodeSandbox demo](https://codesandbox.io/s/github/Oblosys/react-hook-tracer/tree/demo/apps/react-hook-tracer-demo?file=/src/demos/Demo.tsx), which has a checkbox to control console tracing.

### The `useTracer` hook

The `useTracer` hook should be called at the start of the traced component, and returns a record containing the `TracePanel` component and a `trace` function:

```ts
useTracer: (options?: { showProps?: ShowProps }) => { trace: (message: string) => void, TracePanel: () => JSX.Element }
```

The `TracePanel` component can be included in the rendering, and `trace` can be used to emit custom traces to the trace log.

To override how prop values are displayed in the trace log, `useTracer` takes an optional `showProps: ShowProps` argument:

```ts
type ShowProps> = {
[K in keyof Props]?: (propValue: Props[K]) => string
}
```

This can be useful for object prop values, which are stringified by default. For example, if we have a `User` component that takes these props:

```ts
interface UserProps {
user: { name: string; color: string }
count: number
}
```

The trace log will contain entries like `render props: user={"name":"Stimpy","color":"red"} count=1`, which can be made more concise by declaring an override for prop `user`:

```ts
const showProps: ShowProps = { user: ({ name, color }) => `<<${name}:${color}>>` }
```

and in the `User` component call `useTracer({ showProps })`.

Now the log will contain entries like this: `render props: user=<> count=1`.

### List of traced hooks

All traced hooks accept an optional configuration argument, which can be used to specify a custom label that will appear in the trace log and panels. For hooks that keep track of a value, a custom `show` function can be specified as well.

| Hook | Shorthand | Optional configuration argument |
| -------------------- | ------------- | ------------------------------------------------------- |
| `useContext` | `'context'` | `{label?: string, show?: (contextValue: T) => string}` |
| `useMemo` | `'memo'` | `{label?: string, show?: (memoizedValue: T) => string}` |
| `useReducer` | `'reducer'` | See interface `UseReducerTraceOptions` below. |
| `useRef` | `'ref'` | `{label?: string, show?: (refValue: T) => string}` |
| `useState` | `'state'` | `{label?: string, show?: (state: S) => string}` |
| `useCallback` | `'callback'` | `{label?: string}` |
| `useEffect` | `'effect'` | `{label?: string}` |
| `useInsertionEffect` | `'insertion'` | `{label?: string}` |
| `useLayoutEffect` | `'layout'` | `{label?: string}` |

```ts
interface UseReducerTraceOptions {
label?: string
showState?: (state: S) => string
showAction?: (state: A) => string
}
```

### Trace-log message overview

Hooks have different phases in which traces are emitted. The overview below shows all possible phases for each hook.

#### Hooks with values

| Hook | Phase | Appearance in trace log |
| ------------ | ---------- | --------------------------------------------------------------------------------- |
| `useContext` | `init` | On the first render, at the `useContext` call. |
| | `update` | Whenever the context value changes. |
| `useMemo` | `init` | On the first render, at the `useMemo` call. |
| | `refresh` | Whenever the memoized value is recomputed due to changes in the dependencies. |
| `useReducer` | `init` | On the first render, at the `useReducer` call. |
| | `dispatch` | When dispatching an action, shows the action. |
| | `state` | Immediately after a reduction step, shows the updated state. |
| `useRef` | `init` | On the first render, at the `useRef` call. |
| | `set` | Whenever the ref value changes (even if no component re-renders). |
| `useState` | `init` | On the first render, at the `useState` call. |
| | `set` | When setting the state to a value. |
| | `update` | When setting the state with an update function (i.e `setState(prevState => ..)`). |

#### Hooks without values

| Hook | Phase | Appearance in trace log |
| -------------------- | --------- | ------------------------------------------------------------------ |
| `useCallback` | `init` | On the first render, at the `useCallback` call. |
| | `run` | When the callback gets called |
| | `refresh` | When a new callback is created due to changes in the dependencies. |
| `useEffect` | `init` | On the first render, at the `useEffect` call. |
| | `run` | Before the effect runs. |
| | `cleanup` | Before the effect's cleanup function gets called. |
| `useInsertionEffect` | `init` | On the first render, at the `useInsertionEffect` call. |
| | `run` | Before the effect runs. |
| `useLayoutEffect` | `init` | On the first render, at the `useLayoutEffect` call. |
| | `run` | Before the effect runs. |

#### Lifecycle events & `trace`

Even though function components don't have a traditional lifecycle like class components, traced components will emit traces on certain lifecycle events.

| Event | Appearance in trace log |
| ---------- | ---------------------------------------------------------- |
| `mounting` | Just before the component begins to mount. |
| `mounted` | When the component has mounted. |
| `render` | At the start of each render, also shows the current props. |
| `trace` | When the custom `trace` function gets called. |
| `unmount` | Just before the component unmounts. |

### Upcoming features

- For hooks with dependencies, show which dependencies changed.
- A configuration component to replace `setTracerConfig`.
- JSDoc comments for exported hooks.
- Maybe: Show render count in log and panel.