10. Use Case Scenario 4: React Tracked
Understanding React Tracked (using with useState, useReducer, React Redux)
🔖 1. What is React Tracked?
State Usage Tracking
React Tracked is a state usage tracking library that automatically performs rendering optimization based on property detection
It can be used with other state management libraries like Redux, Zustand, etc.
It differs from simple global state management libraries in that it provides rendering optimization functionality
Comparison with Context
When only a specific value is changed, new context values are propagated, and
useContext
detects re-rendering. Therefore, unnecessary re-rendering can occurWhen using React Tracked, you define a
useTracked
hook that can be used instead ofuseContext
. This wraps state with proxy and tracks usage
Example Code
const useFirstName = () => {
const [{ firstName }] = useTracked();
return firstName;
};
Characteristics Seen Through Examples
First, since the usage is the same as
useContext
, it has the advantage of being easy to applyThe code is not much different from when using Context, but internally it tracks state usage and automatically optimizes rendering! A very clever fellow 👍
The methods of using with
useState
anduseReducer
that come later also mainly deal with content similar to the above
🔖 2. Pros and Cons of useTracked
useTracked
Characteristics and Advantages of useTracked
useTracked
As mentioned above, it tracks based on Proxy.
useTracked
wraps state objects with Proxy or uses similar techniques to intercept property (get) access. (Similar content also appeared in Chapter 9: Valtio) As a result, it can record which properties of that state object the component actually read (accessed)Only accessed properties trigger re-rendering. When rendering ends,
useTracked
recognizes "This component depends on properties A, B, C" and only tracks those properties. After that, if any of A, B, C changes, it re-renders the component using that hook. On the other hand, even if unused properties change, this component is not re-rendered. (Very efficient)It performs re-rendering optimization. Since only truly necessary fields from global state or large objects are considered "actual dependencies," unnecessary renders can be greatly reduced. For example, if only
user.name
was accessed, the component won't re-render even ifuser.age
oruser.email
changes
Disadvantages of useTracked
useTracked
There can be performance overhead in the process of recording which properties were read in each rendering process and detecting property access through Proxy. It might not be a big problem in general situations, but it would be good to be careful in cases with complex data structures or frequent rendering
The more deeply nested the state structure is, the more tracking logic there will be. For example, in cases where you need to track properties that go deep like
obj.a.b.c
, unexpected performance costs or unexpected re-rendering might occur. (But this doesn't seem to be only a disadvantage ofuseTracked
)Teams familiar with explicit action/reducer structures like Redux might feel that the automatic tracking provided by
useTracked
is abstract. Since state change flow is not clearly visible, state tracking/debugging might be difficult
📚 References
My Thoughts
This chapter introduces React Tracked, which provides automatic optimization by tracking which properties of state are actually used by components. The key insight is that React Tracked can be used as a drop-in replacement for useContext
while providing much better performance.
The discussion about the pros and cons of useTracked
is particularly valuable - it shows that while the automatic tracking is powerful, it comes with some trade-offs in terms of performance overhead and debugging complexity.
The comparison with Context is important because it shows how React Tracked solves a real problem that many developers face when using React Context for state management.
Code Examples
// ✅ GOOD: Basic React Tracked setup
import { createTrackedSelector } from 'react-tracked';
// Create tracked selector for state
const useTrackedState = createTrackedSelector<AppState>();
// ✅ GOOD: Custom hooks for specific state slices
function useUserName() {
const [{ user }] = useTrackedState();
return user.name; // Only re-renders when user.name changes
}
function useUserEmail() {
const [{ user }] = useTrackedState();
return user.email; // Only re-renders when user.email changes
}
function useUserPreferences() {
const [{ user }] = useTrackedState();
return user.preferences; // Only re-renders when user.preferences changes
}
// ✅ GOOD: Components using tracked state
function UserProfile() {
const name = useUserName(); // Only re-renders when name changes
const email = useUserEmail(); // Only re-renders when email changes
return (
<div>
<h2>{name}</h2>
<p>Email: {email}</p>
</div>
);
}
function ThemeToggle() {
const preferences = useUserPreferences(); // Only re-renders when preferences change
return (
<button
onClick={() =>
updateTheme(preferences.theme === 'light' ? 'dark' : 'light')
}
>
Current theme: {preferences.theme}
</button>
);
}
// ✅ GOOD: Using with useState
function createTrackedState<T>(initialState: T) {
const [state, setState] = useState(initialState);
const useTrackedState = createTrackedSelector<T>();
return {
state,
setState,
useTracked: useTrackedState,
};
}
const { state, setState, useTracked } = createTrackedState({
count: 0,
name: 'Ella',
theme: 'light' as 'light' | 'dark',
});
function Counter() {
const [{ count }] = useTracked(); // Only re-renders when count changes
return (
<div>
<p>Count: {count}</p>
<button
onClick={() => setState((prev) => ({ ...prev, count: prev.count + 1 }))}
>
Increment
</button>
</div>
);
}
function NameDisplay() {
const [{ name }] = useTracked(); // Only re-renders when name changes
return (
<div>
<p>Name: {name}</p>
<input
value={name}
onChange={(e) =>
setState((prev) => ({ ...prev, name: e.target.value }))
}
placeholder='Enter name'
/>
</div>
);
}
// ✅ GOOD: Using with useReducer
type AppAction =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'SET_NAME'; payload: string }
| { type: 'TOGGLE_THEME' };
function appReducer(state: AppState, action: AppAction): AppState {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_NAME':
return { ...state, name: action.payload };
case 'TOGGLE_THEME':
return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
default:
return state;
}
}
function createTrackedReducer<T, A>(
reducer: (state: T, action: A) => T,
initialState: T
) {
const [state, dispatch] = useReducer(reducer, initialState);
const useTrackedState = createTrackedSelector<T>();
return {
state,
dispatch,
useTracked: useTrackedState,
};
}
const { state, dispatch, useTracked } = createTrackedReducer(appReducer, {
count: 0,
name: 'Ella',
theme: 'light' as 'light' | 'dark',
});
function OptimizedCounter() {
const [{ count }] = useTracked(); // Only re-renders when count changes
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
</div>
);
}
function OptimizedNameDisplay() {
const [{ name }] = useTracked(); // Only re-renders when name changes
return (
<div>
<p>Name: {name}</p>
<input
value={name}
onChange={(e) =>
dispatch({ type: 'SET_NAME', payload: e.target.value })
}
placeholder='Enter name'
/>
</div>
);
}
// ✅ GOOD: Using with Redux
import { useSelector } from 'react-redux';
import { createTrackedSelector } from 'react-tracked';
// Create tracked selector for Redux state
const useTrackedReduxState = createTrackedSelector<RootState>();
function ReduxCounter() {
const [{ counter }] = useTrackedReduxState(); // Only re-renders when counter changes
return (
<div>
<p>Count: {counter.value}</p>
<button onClick={() => dispatch(increment())}>Increment</button>
</div>
);
}
function ReduxUserProfile() {
const [{ user }] = useTrackedReduxState(); // Only re-renders when user changes
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
</div>
);
}
// ✅ GOOD: Performance comparison
function PerformanceComparison() {
// Without React Tracked - re-renders on any state change
const state = useSelector((state: RootState) => state);
// With React Tracked - only re-renders when accessed properties change
const [{ user, counter }] = useTrackedReduxState();
return (
<div>
<p>User: {user.name}</p>
<p>Count: {counter.value}</p>
</div>
);
}
// ✅ GOOD: Custom tracked selectors for complex state
function useUserTheme() {
const [{ user }] = useTrackedReduxState();
return user.preferences.theme; // Only re-renders when theme changes
}
function useUserNotifications() {
const [{ user }] = useTrackedReduxState();
return user.preferences.notifications; // Only re-renders when notifications change
}
function ThemeToggle() {
const theme = useUserTheme(); // Only re-renders when theme changes
return (
<button onClick={() => dispatch(toggleTheme())}>
Current theme: {theme}
</button>
);
}
function NotificationToggle() {
const notifications = useUserNotifications(); // Only re-renders when notifications change
return (
<label>
<input
type='checkbox'
checked={notifications}
onChange={(e) => dispatch(toggleNotifications(e.target.checked))}
/>
Enable notifications
</label>
);
}
Last updated