Skip to content

Allow filtering state properties before save it to history #237

@GeorgeGkas

Description

@GeorgeGkas

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions