NOTE This article started as a reply to a Slack message but morphed into something larger. It is not a complete discussion, but just some thoughts I had around the topic of state management. View discretion is advised.
With local state it is easy to modify things you shouldn't or take actions that should be responded to somewhere else.
With Centralized state you now have to detangle data from small areas of your app meaning you will likely have identifiers or lists where you may not have had lists before. This causes you to have your persisted data model interlaced with local only visual state information. This also causes a large amount of information to have to "pass through" or be ignored by large areas of your application. Without proper guidance, tooling, or caution: centralized state can allow items to accidentally modify data they may not even have access to at all!
Example:
Let's say we have a file tree which is persisted in the following JSON data:
{
"name": "src",
"folder": true,
"children": [
{
"name": "app.js",
"folder": false
},
{
"name": "index.html",
"folder": false
},
{
"name": "imgs",
"folder": true,
"children": [
{
"name": "face.jpg",
"folder": false
},
]
}
]
}
Now we want to make a toggleable file tree.
For this example, we'll create a basic standard MVC application:
// Controller
class FileTreeController {
createNewFile() {
}
createNewFolder() {
}
attach() {
this.model.children.forEach((file) => {
const v = new FileTreeView(file, this);
v.render();
v.attach(parentElement);
})
}
}
// View
class FileTreeView {
constructor(model, controller) {
this.model = model;
this.controller = controller;
}
render() {
// Make changes to UI
}
toggle() {
// To be implemented
}
}
If we use local state to manage our folder open/closed status, we can do something like this:
class FileTreeView {
constructor(model, controller) {
this.model = model;
this.controller = controller;
this.showChildren = false;
}
render() {
// Make changes to UI
}
toggle() {
this.showChildren = true;
this.render();
}
}
One thing to note is that our original model is pure, we don't need to modify anything. This means persisting the core file structure is clean. But, it comes at a heavier cost since we don't have a good way to persist folder toggle state. If you don't need this persisted: well that's it use local state to toggle the children showing or hiding.
If you need the toggle state to persist, you will need to instead rewrite the View to be a bit more like this:
class FileTreeView {
constructor(model, controller) {
this.model = model;
this.controller = controller;
this.showChildren = false;
}
render() {
// Make changes to UI
}
toggle() {
this.controller.toggle(this.model);
}
}
Then the controller will update to be something more like this:
class FileTreeController {
// ... methods from before
toggle(file) {
// traverse entire model state and set toggle state for specified file
// make decisions to persist state tree
// what has changed?
// do we need separate storage persistence for toggle vs actual files?
}
}
Sorry for the puesdo code, but I've been really wanting to finally show an example of this out in the wild.
A note about reactive central state management tools
If this title confuses you, it's "Flux" architecture that I'm talking about. In the reactive controller example I say something like "traverse entire model state and set toggle state for specified file". This is a pretty scary thought and should be really considered! While Redux and other reactive toolsets minimize the surface area of this process, by encouraging small reducer functions: it should not be overlooked that the entire state tree is traversed for ever decision.
Like-wise, middleware and other practices help with the question "make decisions to persist state tree". I think it may come off that this decision is something simple, but it's not. There is a lot of time and work going into making these decisions, but it's hard!
Personally, I'm not quite sure what I would do for persisting state for toggled items.
Part of me dreads the idea of pulling apart properties and figuring out which properties should be persisted to two different persistence stores.
The other side of me thinks of this as an RDBMS system and thinks that the file tree is parsable with relatively unique keys (the full file path),
so if I can key on that data then I can have my application controller or a singleton service manage persistence of that data.
This means that when I want to work with this information, I would call out and ask "hey is app/img
open or closed"