In the previous chapter, React Integration Layer, we connected our Store to the UI. We learned how to make buttons update the state and how to make the screen re-render automatically.
However, a modern application doesn't live in a vacuum. It interacts with the outside world:
If we put all this logic inside our React components, our code would become a messy web of duplicate logic. We need a better way.
We need State Synchronization.
Let's go back to our analogy of the Central Whiteboard (the Store).
Imagine you write "Enable Night Mode" on the whiteboard. The team sees it and puts on sunglasses (the UI updates). But if the building loses power (the app closes), that information is lost.
We need a dedicated Clerk. The Clerk stands next to the whiteboard and watches it like a hawk.
This Clerk is our Side Effect Handler. It ensures the "Inside World" (RAM) stays in sync with the "Outside World" (Disk/Server).
Let's look at a real example. Our app has a verbose setting (detailed logging).
The "Bad" Way (Doing it inside the button):
// Inside a React Component
function handleToggle() {
// 1. Update the Store
store.setState(prev => ({ ...prev, verbose: true }));
// 2. Save to Disk (Side Effect)
saveToDisk({ verbose: true });
}
Problem: What if verbose is turned on via a keyboard shortcut? Or a voice command? We would have to copy step #2 everywhere.
The "Synchronization" Way:
We simply watch the state. If verbose changes for any reason, we save it.
To implement this, we use a single function called onChangeAppState. It runs automatically whenever the state changes.
It receives two arguments:
oldState: What the board looked like a moment ago.newState: What the board looks like now.We compare them to decide what to do.
We check if the specific value we care about is different.
// onChangeAppState.ts
export function onChangeAppState({ newState, oldState }) {
// Did the 'verbose' flag change?
if (newState.verbose !== oldState.verbose) {
console.log("Verbose mode just toggled!");
// ... trigger side effect here ...
}
}
If they are different, we execute the external action (like saving to configuration).
// ... inside the if statement ...
// Save the new value to the global config file
saveGlobalConfig(currentConfig => ({
...currentConfig,
verbose: newState.verbose
}))
Explanation: Now, it doesn't matter how the state changed. As long as the Store updates, the file on the disk updates.
This system acts as a middleware pipeline.
Our project state uses this pattern for critical infrastructure. Let's look at two complex examples from onChangeAppState.ts.
When a user changes their Settings (like pasting a new API Key), we must immediately clear any cached authentication tokens. If we don't, the app might keep trying to use the old, broken key.
// onChangeAppState.ts (Simplified)
// Check if the entire 'settings' object changed
if (newState.settings !== oldState.settings) {
// 1. Clear AWS Credentials
clearAwsCredentialsCache()
// 2. Clear Google Cloud Credentials
clearGcpCredentialsCache()
// 3. Clear API Key Helpers
clearApiKeyHelperCache()
}
Beginner Note: Notice we don't check which setting changed. If settings changed at all, we wipe the security caches just to be safe. This makes the system robust.
In our app, we have different "Permission Modes" (e.g., "Plan Mode" vs "Code Mode"). When this changes, we need to tell our backend server (CCR) so it knows how to behave.
// onChangeAppState.ts (Simplified)
const prevMode = oldState.toolPermissionContext.mode
const newMode = newState.toolPermissionContext.mode
// Did the mode change?
if (prevMode !== newMode) {
// Notify the backend (CCR) immediately
notifySessionMetadataChanged({
permission_mode: newMode
})
}
Beginner Note: This ensures that if the user changes the mode using a slash command (/plan), the backend is notified instantly, even though the command didn't explicitly call notifySessionMetadataChanged.
onChangeAppState.ts) that defines all the side effects in the entire app.In this tutorial series, we have built a complete State Management architecture:
By separating these concerns, we have created an application that is easy to read, easy to test, and easy to extend. You can now add new features by simply adding a property to AppState, creating a Selector, and adding a sync ruleβwithout ever fighting with "spaghetti code."
End of Tutorial.
Generated by Code IQ