A Detailed Guide to the track-dependency-changes Package: Tracking Dependency Changes in React Components

Mehul Kothari
8 min readSep 18, 2024

--

Introduction

In modern React applications, debugging and optimizing performance often revolves around identifying unnecessary re-renders and pinpointing the root cause of those re-renders. Managing state updates and dependencies in a way that minimizes redundant rendering is crucial to maintaining performant applications, especially as the complexity of components grows.

To tackle this challenge, I’ve created the npm package track-dependency-changes, which is designed to help developers track dependency changes in React components. This utility not only logs changes in state and props before and after renders but also highlights which dependencies trigger re-renders. It’s a lightweight, flexible, and highly readable tool that makes debugging much easier.

In this blog, I’ll walk you through the package’s features, explain how to install and use it, and discuss practical scenarios where it can be a game-changer for performance debugging in React applications.

Package Link

Git Repo

Not sure how to publish an npm package using React?

If you’re interested in publishing your own npm package or want to understand the process, check out my guide on how to publish an npm package with React using javascript and typescript:

Why Track Dependency Changes?

React’s re-render mechanism can sometimes be tricky to follow, particularly when components re-render unexpectedly. Identifying which state or prop changes are causing these renders can be time-consuming without proper tools.

Here are some common scenarios where unnecessary re-renders occur:

  1. State updates that don’t actually change the component visually but still cause a re-render.
  2. Functions defined inside the component (e.g., in useEffect, useCallback, etc.) that can change references between renders.
  3. Complex objects or arrays that are being mutated instead of recreated, causing React to re-render when it shouldn’t.

This package addresses these issues by giving developers a clear snapshot of each component’s dependencies before and after each render, while counting the number of re-renders.

Features of track-dependancy-changes

  • Track Dependency Changes: Observe and log changes to any dependencies (state, props, or callback functions) between renders.
  • Render Count: Keep track of how many times a component has re-rendered.
  • Dependency Aliasing: Define custom labels for dependencies for easier identification in logs.
  • Detailed Change Log: View old and new values for each dependency, along with a clear indication of whether it changed (✔️ or ).

Installation

To start using the track-dependency-changes package, install it via npm:

npm install track-dependancy-changes

How to Use useTrackDependency

Once installed, the package provides a custom hook, useTrackDependency, that you can use in your React components. Let’s dive into an example of how to integrate it:

import React, { useState, useEffect, useCallback } from "react";
import {useTrackDependency} from "track-dependancy-changes"; // Import the hook

const ExampleComponent: React.FC = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState("Hello");
const [complexState, setComplexState] = useState({ name: "Alice", age: 25 });

// Callback function
const handleClick = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);

// Track dependencies with the hook
useTrackDependency(
[count, text, complexState, handleClick], // List of dependencies
["Count", "Text", "Complex State", "Handle Click"], // Aliased names for better logs
"ExampleComponent" // Optional component name for console logging
);

return (
<div>
<h1>useTrackDependency Example</h1>
<p>Count: {count}</p>
<button onClick={handleClick}>Increase Count</button>

<p>Text: {text}</p>
<button onClick={() => setText(text === "Hello" ? "World" : "Hello")}>
Toggle Text
</button>

<p>Complex State: {JSON.stringify(complexState)}</p>
<button onClick={() => setComplexState({ ...complexState, age: complexState.age + 1 })}>
Increase Age
</button>
</div>
);
};

export default ExampleComponent;

Understanding the Hook

The useTrackDependency hook makes debugging effortless by logging the state of tracked dependencies at every render. The key parameters it takes include:

  1. Dependencies: An array of state, props, or functions you wish to monitor.
  2. Labels: Aliased names for the dependencies (useful in complex components with multiple states/props).
  3. Component Name: The name of the component (optional), which helps with identifying logs when multiple components use this hook.

Console Output

Each time the component renders, a detailed table is printed in the console, making it easy to identify which dependencies have changed. Here’s an example output for the ExampleComponent after two renders:

ExampleComponent Rendering 2 times
┌─────────┬────────────────────┬──────────┬───────────┬─────────┐
│ (index) │ Dependency │ Old Value│ New Value │ Change │
├─────────┼────────────────────┼──────────┼───────────┼─────────┤
│ 0 │ Count │ 0 │ 1 │ ✔️ │
│ 1 │ Text │ "Hello" │ "Hello" │ ❌ │
│ 2 │ Complex State │ {name:…} │ {name:…} │ ❌ │
│ 3 │ Handle Click │ func │ func │ ❌ │
└─────────┴────────────────────┴──────────┴───────────┴─────────┘

This output clearly shows which dependencies have changed (with a ✔️) and which have stayed the same (with a ).

Practical Use Cases

The track-dependancy-changes package can be particularly useful in the following scenarios:

1. Complex Forms

When working with multi-step forms, you may have many fields and states to track, and some might cause re-renders unnecessarily. Using this hook, you can monitor which state changes are causing renders and optimize accordingly.

2. Performance Optimization

In large-scale applications with nested components, small, avoidable state changes can ripple across multiple components, leading to excessive re-renders. By using this tool, you can target exactly where the re-renders originate and refactor your code to improve performance (e.g., by using useMemo, useCallback, etc.).

3. Callback Functions and Object References

React components can re-render due to reference changes in functions (callbacks) or complex state objects. The track-dependancy-changes package helps identify when and where these reference changes happen, enabling you to optimize and memoize functions or use immutable patterns with state updates.

Lets take one practical example

Use Case: Fetching User Data

Imagine we have a component that fetches user data from an API based on the user’s ID. The component allows updating the user’s information, but we want to ensure that the API call is made only when the actual user ID changes, not just when the component re-renders with the same user details.

Optimized Component Code

Here’s how we can optimize the component using track-dependancy-changes to monitor state changes and ensure efficient API calls.

import { useTrackDependency } from "track-dependancy-changes";
import React, { useState, useEffect, useCallback, useMemo } from "react";

const UserDataFetcher: React.FC = () => {
// State to hold fetched user data
const [userData, setUserData] = useState(null);
// State to manage user details
const [user, setUser] = useState({
id: 1,
firstName: 'John',
lastName: 'Doe'
});
// State to manage loading status
const [isLoading, setIsLoading] = useState(false);


// Function to fetch user data (tracked function)
const fetchUserData = useCallback(async () => {
setIsLoading(true);
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${memoizedUser.id}`);
const result = await response.json();
setUserData(result);
setIsLoading(false);
}, [user]);

// Use the hook to track dependencies and debug re-renders
useTrackDependency(
[userData, memoizedUser, isLoading, fetchUserData], // Dependencies to track
["User Data", "Memoized User", "Is Loading", "Fetch User Data"], // Aliases for readability
"UserDataFetcher" // Component name for logs
);

// Effect to trigger fetchUserData on memoizedUser change
useEffect(() => {
fetchUserData();
}, [fetchUserData]);

// Function to simulate updating user details
const updateUser = () => {
setUser({
id: 1,
firstName: 'John',
lastName: 'Doe'
});
};

return (
<div>
<h1>User Data Fetcher</h1>
{isLoading ? <p>Loading...</p> : <p>User Data: {JSON.stringify(userData)}</p>}
<button onClick={updateUser}>Update User</button>
</div>
);
};

export default UserDataFetcher;

Console Logs

When you click the “Update User” button with the above code, the logs will look like this

UserDataFetcher Rendering 2 times
┌─────────┬───────────────────┬──────────┬───────────┬─────────┐
│ (index) │ Dependency │ Old Value│ New Value │ Change │
├─────────┼───────────────────┼──────────┼───────────┼─────────┤
│ 0 │ User Data │ {…} │ {…} │ ✔️ │
│ 1 │ User │ {id:1…} │ {id:1…} │ ✔️ │
│ 2 │ Is Loading │ false │ true │ ✔️ │
│ 3 │ Fetch User Data │ func │ func │ ❌ │
└─────────┴───────────────────┴──────────┴───────────┴─────────┘
Fetching data for user ID: 1

Explanation:

  1. Re-render with New Reference: Each time the “Update User” button is clicked, the setUser function creates a new user object. Even though the object's content is the same, its reference changes.
  2. Effect Triggered: The fetchUserData function is dependent on the user object. Since its reference changes, the useCallback hook considers it a new function, thus triggering the useEffect hook and making the API call.
  3. Logs Show Change: The console logs reflect that User dependency changed, leading to an API call even though the actual user data hasn’t changed

With Memoization

Updated Component Code

Here’s the same component but with the user object memorized:

// Memoized version of user object to prevent unnecessary updates
const memoizedUser = useMemo(() => user, [user.id, user.firstName, user.lastName]);

// Function to fetch user data (tracked function)
const fetchUserData = useCallback(async () => {
setIsLoading(true);
console.log("Fetching data for user ID:", memoizedUser.id);
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${memoizedUser.id}`);
const result = await response.json();
setUserData(result);
setIsLoading(false);
},
[memoizedUser]);

After Clicking “Update User” Button:

UserDataFetcher Rendering 2 times
┌─────────┬───────────────────┬──────────┬───────────┬─────────┐
│ (index) │ Dependency │ Old Value│ New Value │ Change │
├─────────┼───────────────────┼──────────┼───────────┼─────────┤
│ 0 │ User Data │ null │ {…} │ ✔️ │
│ 1 │ Memoized User │ {id:1…} │ {id:1…} │ ❌ │
│ 2 │ Is Loading │ false │ true │ ✔️ │
│ 3 │ Fetch User Data │ func │ func │ ❌ │
└─────────┴───────────────────┴──────────┴───────────┴─────────┘

Explanation:

  1. Stable Memoized Reference: By using useMemo, the reference of the memoizedUser object remains stable unless the properties (id, firstName, lastName) change. Since the user properties are the same, memoizedUser does not change its reference.
  2. Effect Triggered: With a stable memoizedUser, the fetchUserData function remains consistent across re-renders. Thus, no unnecessary API calls are triggered when the user state updates with the same values.
  3. Logs Show Stability: The logs indicate that Memoized User remains unchanged, preventing additional API calls. This demonstrates how memoization optimizes performance by avoiding redundant fetches.

Conclusion

The track-dependancy-changes package offers a handy way to monitor state and prop changes in React components, helping developers optimize their applications and avoid unnecessary re-renders. Its clean console output, dependency aliasing, and render tracking simplifies the debugging process while improving overall performance.

Give it a try and start tracking your component dependencies with ease!. You can find the package information and its code below

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Mehul Kothari
Mehul Kothari

Written by Mehul Kothari

Mean stack and Full stack Developer. Open to work as freelancer for designing and developing websites.

No responses yet

Write a response