-
-
Notifications
You must be signed in to change notification settings - Fork 189
Description
Intro
The current version 1.0.0-beta9-9-7
does not provide a solution to customize the current state before saving it to history (thought insert
). This is a crucial feature when we have a complex reducer with high update rate. In such cases, we can't break the reducer to multiple parts or filter
out actions, cause the coupling between them is high. Such use cases can be found in graphic applications where the trigger of an operation dispatch multiple actions. Consider the following example:
Use case: A simple graphic app
Bellow is a simple HTML canvas
Pressing down the n
key in our keyboard allow us to create nodes
We can select a node by clicking on it
We can create a temporary arrow by holding down the SHIFT key
By clicking another node, when have already selected one, we can create an edge between the two nodes
Dragging one node dispatch two actions: one to update the position of the selected node and one to redraw the arrow
Here is a simple state representation of the above use case:
const initialState = {
// contain objects, each representing a node
nodes: {},
// the current selected node
selectedNode: null,
// The cursor position, helps when we need to show the temp arrow
cursor: {
x: 0,
y: 0,
},
// if we've selected a node and hold the SHIFT key then we have to create an arrow
drawTempArrow: false,
// contain objects, each representing an arrow
arrows: {},
}
The problem
The difficult part to manage the above state is when the properties relate to each other. Consider that the user has selected a node, this action updates the selectedNode
property, and decides to UNDO
. What will happen is that the selected
node will be saved to the history as is. When the user dispatch REDO
, the state now can be entirely different, due to reasons outside the Redux store. This might break the whole app.
Solution
The solution to the above problem is to reset the selectedNode
property to null
before save it to the history.
What I propose is to add one more property filterStateProps
to the configuration of undoable
enhancer. The property will be an function filterStateProps (currentState)
with a parameter to the current unsaved state, i.e. the state before inserting it to history. The return value will be the final state that will be saved. Here is an example using this solution:
/**
* Redux root reducer.
*/
import { combineReducers } from 'redux'
import undoable from 'redux-undoable'
import editor from './app/editor'
export default () =>
combineReducers({
editor: undoable(editor, {
limit: 10,
filterStateProps(state) {
return {
arrows: state.arrows,
nodes: state.nodes,
selectedNode: null,
cursor: {
x: 0,
y: 0,
},
drawTempArrow: false,
}
},
}),
})
This approach has a number of advantages:
- Backward compatible feature
- Easy API
- Easy implementation without "touching many things"
- Useful in such use cases
- The best solution if we need to manipulate the state tree before save to history
I strongly believe that a feature like this can make the difference when working with apps that require frequent updates. I have already implemented and tested this approach to my own projects and works beautifully.