While thinking about enhancements and new features for Minimux, there's one idea that I keep coming back to: composition
What is it?
In simple terms, you could define a reducer for a small object with a little bit of state without having to worry about the rest of your application, and some parent element could combine that reducer with the other reducers in your app.
As a simple example, consider the following state:
{
"player1": {
"health": 100,
"currentWeapon": 2,
"weapons": [
{
"type": "pistol",
"currentAmmo": 3,
"maxAmmo": 6
},
{
"type": "rifle",
"currentAmmo": 1,
"maxAmmo": 1
}
]
},
"player2": {
"health": 50,
"currentWeapon": 1,
"weapons": [
{
"type": "rpg",
"currentAmmo": 3,
"maxAmmo": 3
}
]
}
}
Without composition, for a gun to know it's "current ammo" it has to look in state.playerX.weapons[Y].currentAmmo (which also means keeping track of X and Y somehow)
With composition a gun only has to look in state.currentAmmo, because the gun's state is limited in scope.
Advantages
Without composition, we need to be aware of the entire state tree to write our reducer:
// "SHOOT" action
minimux.register((state, action) => {
// A better implementation may exist, but my inability to figure it out within a few minutes just drives home how complicated this can get
// Duplicate array
let otherWeapons = state.player1.weapons.slice();
// Modify new array
let currentWeapon = state.player1.weapons[state.player1.currentWeapon];
currentWeapon.currentAmmo--;
// Update state (using spread operator)
return {...state, player1: {...state.player1, weapons: otherWeapons}};
});
If we had composition then we could define the "SHOOT" action without concern for the whole hierarchy. The weapon would be blissfully unaware of the rest of your app:
minimux.register((state, action) => {
return {...state, currentAmmo: state.currentAmmo - 1};
});
This means you write less code and your app is easier to understand.
It also makes your app modular. If you define the reducers and state for a "phone number input", you could re-use that same input in 10 different web apps and in 10 different contexts. Just like a React component can be used and re-used, a modular "state component" could be used and re-used.
Disadvantages
Even if behind the scenes the state is stored in a single, global store -- making the state modular like this encourages thinking of each component as having its own state. From our simple FPS example above, suppose we wanted to figure out the total ammo across all guns. A naive approach might ask each gun for their ammo, making the approach no better than class-based React components with internal state.
The addition of reducer and listener composition also feels like the first step towards turning Minimux into a full-blown state management framework, which is about as far from minimalist as you can get. The idea I had in mind when creating Minimux was that I wanted to get up and running with a centralized, global state store in seconds. I didn't want to learn a new paradigm just to use the library. If using Minimux means understanding the concept of reducer composition and rethinking your app, it's turning into something bigger than what I had originally imagined.
There are also implementation concerns. From the perspective of the front-end API, adding a way to compose reducers and listeners is no different from React's createClass and createElement (where createElement can take a list of classes as a parameter). I would have something like defineReducer and instantiateReducer where instantiateReducer could optionally take an object or array of reducers to be "child reducers". At this point you're adding something to almost every component like so:
class MyComponent extends React.Component {
...
}
minimux.instantiateReducer(MyComponentReducer, ...);
If we're going that far, we might as well pull the data into the component and truly make it part of the component (extend React.Component with Minimux.Component, maybe?)
From the perspective of the back-end, we would need to consider the performance and memory implications. If I bound a listener to the rifle in my FPS example then how would I do it? Would it look like: minimux.listen("player1.weapons[1]", callback)? I could parse that string once and store a reference to the weapon, because every time the state is updated the weapon is replaced with a new one and the reference would have to be updated. That means parsing that string every time the state changes. I might instead have a tree structure that mimics the structure of the reducers and recursively walk it looking for changes (can be tested by reference, e.g.: state.player1 === newState.player1).
And then what if a gun wanted access to another gun's information? We could say "just won't happen", as it's supposed to be compartmentalized and modular. But we have a global store. So shouldn't it be possible for any part of the application to access any part of the state? The reason I see something like this might happen, I think about Facebook's chat example when discussing React. If the chat window had a list of all messages, the "new messages" icon would also want to know when new messages had arrived. We could warp both the icon and the chat window in a single "container", but then we're just dealing with Redux. Also what if we wanted to extend the capability in the future? We couldn't just take our chat component and add another component, we'd have to modify the original chat component in order to have access to its state.
Discussion
I've presented some pros (less code, easier to understand, fewer bugs) and cons (state feels distributed, performance issues, harder to extend) to composition.
At this point I'm undecided on whether or not it's a good idea. I'd like to open this up to discussion to get ideas.
While thinking about enhancements and new features for Minimux, there's one idea that I keep coming back to: composition
What is it?
In simple terms, you could define a reducer for a small object with a little bit of state without having to worry about the rest of your application, and some parent element could combine that reducer with the other reducers in your app.
As a simple example, consider the following state:
{ "player1": { "health": 100, "currentWeapon": 2, "weapons": [ { "type": "pistol", "currentAmmo": 3, "maxAmmo": 6 }, { "type": "rifle", "currentAmmo": 1, "maxAmmo": 1 } ] }, "player2": { "health": 50, "currentWeapon": 1, "weapons": [ { "type": "rpg", "currentAmmo": 3, "maxAmmo": 3 } ] } }Without composition, for a gun to know it's "current ammo" it has to look in
state.playerX.weapons[Y].currentAmmo(which also means keeping track of X and Y somehow)With composition a gun only has to look in
state.currentAmmo, because the gun's state is limited in scope.Advantages
Without composition, we need to be aware of the entire state tree to write our reducer:
If we had composition then we could define the "SHOOT" action without concern for the whole hierarchy. The weapon would be blissfully unaware of the rest of your app:
This means you write less code and your app is easier to understand.
It also makes your app modular. If you define the reducers and state for a "phone number input", you could re-use that same input in 10 different web apps and in 10 different contexts. Just like a React component can be used and re-used, a modular "state component" could be used and re-used.
Disadvantages
Even if behind the scenes the state is stored in a single, global store -- making the state modular like this encourages thinking of each component as having its own state. From our simple FPS example above, suppose we wanted to figure out the total ammo across all guns. A naive approach might ask each gun for their ammo, making the approach no better than class-based React components with internal state.
The addition of reducer and listener composition also feels like the first step towards turning Minimux into a full-blown state management framework, which is about as far from minimalist as you can get. The idea I had in mind when creating Minimux was that I wanted to get up and running with a centralized, global state store in seconds. I didn't want to learn a new paradigm just to use the library. If using Minimux means understanding the concept of reducer composition and rethinking your app, it's turning into something bigger than what I had originally imagined.
There are also implementation concerns. From the perspective of the front-end API, adding a way to compose reducers and listeners is no different from React's
createClassandcreateElement(wherecreateElementcan take a list of classes as a parameter). I would have something likedefineReducerandinstantiateReducerwhereinstantiateReducercould optionally take an object or array of reducers to be "child reducers". At this point you're adding something to almost every component like so:If we're going that far, we might as well pull the data into the component and truly make it part of the component (extend React.Component with Minimux.Component, maybe?)
From the perspective of the back-end, we would need to consider the performance and memory implications. If I bound a listener to the rifle in my FPS example then how would I do it? Would it look like:
minimux.listen("player1.weapons[1]", callback)? I could parse that string once and store a reference to the weapon, because every time the state is updated the weapon is replaced with a new one and the reference would have to be updated. That means parsing that string every time the state changes. I might instead have a tree structure that mimics the structure of the reducers and recursively walk it looking for changes (can be tested by reference, e.g.:state.player1 === newState.player1).And then what if a gun wanted access to another gun's information? We could say "just won't happen", as it's supposed to be compartmentalized and modular. But we have a global store. So shouldn't it be possible for any part of the application to access any part of the state? The reason I see something like this might happen, I think about Facebook's chat example when discussing React. If the chat window had a list of all messages, the "new messages" icon would also want to know when new messages had arrived. We could warp both the icon and the chat window in a single "container", but then we're just dealing with Redux. Also what if we wanted to extend the capability in the future? We couldn't just take our chat component and add another component, we'd have to modify the original chat component in order to have access to its state.
Discussion
I've presented some pros (less code, easier to understand, fewer bugs) and cons (state feels distributed, performance issues, harder to extend) to composition.
At this point I'm undecided on whether or not it's a good idea. I'd like to open this up to discussion to get ideas.