02. Using Local and Global State
About: Prop drilling pattern, what is global state?, when is it good to use global state?
🔖 1. Problems in Our Project Code and Solution Direction
Book Content
If you create local state and only use that state in components and child components, it's good in terms of locality and reusability. Therefore, following this strategy is usually recommended.
However, in certain situations, there are cases where common state is needed for two or more components that are far apart from each other in the tree. In such cases, global state is needed. Since global state doesn't belong to a specific component, we need to consider where to store the global state.
Current: Managing with Context
It's expected that common state will be needed for components like Header, Main, Panel that are far apart from each other in the tree, so states are managed globally
e.g. Currently using the state representing the current directory path (
curDir
) globally, andStorageContextProvider
exists withinStorageContext.tsx
Change: Managing with Props
Global state management was overused, making state tracking difficult
Since the state is relatively simple and updates don't occur frequently, it was judged as over-engineering
Therefore, it was judged that managing with props would be simpler and more efficient
So, we changed to a method where the parent component (
Storage.tsx
) creates local state and components and child components (Header, Main, Panel) receive and use arguments, i.e., propsIf necessary, consider using Context partially or mixing approaches
e.g.
curDir
can only be used in each component that needs path management
Connecting with Book Content
[!NOTE]
What global states are used in our code?
And how much and where are those global states being used?
What components are experiencing unnecessary re-renders?
🔖 2. Pure Functions vs. Callback Functions
Book Content
Functions that depend on global variables work identically as long as the variables don't change. Since the function's behavior can be changed from outside, it can be considered a powerful feature. However, the downside is that this function can be used elsewhere without knowing that it depends on external variables.
When variables are singletons, creating a container object to wrap global variables is a more modularized approach to further increase code reusability.
What I've Been Refactoring Lately
Recently, while checking if our project code is well modularized or checking the overall modularization, I discovered the following problem and am currently refactoring.
Callback functions were included inside pure functions, increasing the function's responsibility and causing side effects.
For example, calling
getDirectory
as a callback inside therenameDirectory
function made therenameDirectory
function not only change directory names but also take on the role of getting changed directory information, violating the single responsibility principle.Additionally, it reduced function reusability and increased dependencies between functions, hindering code modularization and maintainability.
Before: Pure Function Including Callback Function
function renameDirectory(oldName, newName, callback) {
// Directory name change logic
callback(newName);
}
// Usage example
renameDirectory('oldDir', 'newDir', (updatedName) => {
const directoryInfo = getDirectory(updatedName);
// Additional logic
});
After Refactoring: Combining Pure Functions
function renameDirectory(oldName, newName) {
// Directory name change logic
return newName;
}
function getDirectory(directoryName) {
// Logic to get directory information
return directoryInfo;
}
// Function combination
const newDirName = renameDirectory('oldDir', 'newDir');
const directoryInfo = getDirectory(newDirName);
Connecting with Book Content
[!NOTE] When global variables are singletons, creating a container object to wrap global variables is a more modularized approach. This is similar to combining pure functions as shown in the example above to pass necessary data. Using container objects allows functions to access necessary data through the container, so they don't directly depend on global variables and dependencies become clear.
🔖 3. The Meaning of 'Ensuring Locality'
Connecting with Book Content
The book says "Functions containing
useState
can be said to be 'suppressed' because variables can only be used within the function declaration scope," and the reason is that it's impossible to change variables from outside the function.Based on this point, when nothing outside the component has any influence, when the component's independence is guaranteed, that is, when locality is guaranteed.
🔖 4. When to Use Global State?
When Passing Props is Not Appropriate
Let's draw the component tree.
At this time, if state needs to be shared between two components that are far apart from each other?
What if we even need to lift state from depth 3 to the root?
Since props must be passed through intermediate components, this is very inefficient.
For example, if we need to pass the same prop through
Storage.tsx
,StorageMain.tsx
, andStorageSub.tsx
forStorageHeader.tsx
(even though the listed components don't use that prop), how would someone seeing that code for the first time feel?
When State Already Exists Outside React
For example, there might be user authentication information obtained without React. In this case, global state can exist outside React.
In addition, websockets received in real-time from servers, network status, clipboard data, etc. may also need to be managed globally.
🔖 5. Key Points of Chapter 1
Ask These Questions When Writing Code
[!NOTE] Does each component exist independently? Are there unnecessary dependencies that don't affect performance?
My Thoughts
This chapter provides a clear framework for deciding when to use local vs. global state. The key insight is that we should default to local state and only use global state when there's a compelling reason. The examples of prop drilling and external state sources are particularly helpful for making these decisions.
The refactoring example from callback functions to pure functions is excellent - it shows how we can improve code modularity by reducing dependencies and making functions more focused. This principle applies beyond just state management.
Code Examples
// ✅ GOOD: Local state management
function LocalStateExample() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// ✅ GOOD: Prop drilling for simple cases
function ParentComponent() {
const [sharedData, setSharedData] = useState('initial');
return (
<div>
<ChildComponent data={sharedData} onUpdate={setSharedData} />
</div>
);
}
// ✅ GOOD: Context for complex state sharing
const GlobalStateContext = createContext<{
user: User | null;
setUser: (user: User) => void;
}>({
user: null,
setUser: () => {},
});
function GlobalStateProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
return (
<GlobalStateContext.Provider value={{ user, setUser }}>
{children}
</GlobalStateContext.Provider>
);
}
// ✅ GOOD: Pure functions without side effects
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
function formatCurrency(amount: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount);
}
// Usage: Combine pure functions
const total = calculateTotal(cartItems);
const formattedTotal = formatCurrency(total);
Last updated