omnidan / redux-undo

:recycle: higher order reducer to add undo/redo functionality to redux state containers
MIT License
2.91k stars 188 forks source link

Field extensions #264

Closed nmay231 closed 4 years ago

nmay231 commented 4 years ago

Please check if the PR fulfills these requirements

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)

Adds a config option extension that allows the user to add different "field extensions." The combineExtensions helper allows the user to use multiple at once.

Included is the much requested flattenState and actionField.

What is the current behavior? (You can also link to an open issue here)

Requested in #179 and #233.

There are also WIP versions for helping with #150 and #237.

What is the new behavior?

Extensions would be added like so.

undoable(reducer, {
  extension: actionField({ insertMethod: 'inline' })
})

// alternatively, use combineExtensions to add multiple extensions
undoable(reducer, {
  extension: combineExtensions(
    flattenState(),
    actionField({ includeAction: (action) => action.included !== undefined })
  ),
  disableWarnings: true
})

disableWarnings disables the warnings that are logged by each field extension that describes how some state might be overridden.

Extension descriptions:

/**
 * The flattenState() field extension allows the user to access fields normally like
 * `state.field` instead of `state.present.field`.
 *
 * Warning: if your state has fields that conflict with redux-undo's like .past or .index
 * they will be overridden. You must access them as `state.present.past` or `state.present.index`
 */

/**
 * The actionField() field extension allows users to insert the last occuring action
 * into their state.
 *
 * @param {Object} config - Configure actionField()
 *
 * @param {string} config.insertMethod - How the last action will be inserted. Possible options are:
 *   - actionType: { ...state, actionType: 'LAST_ACTION' }
 *   - action: { ...state, action: { type: 'LAST_ACTION', ...actionPayload } }
 *   - inline: { ...state, present: { action: { type: 'LAST', ...payload }, ...otherFields } }
 *
 * @param {actionFieldIncludeAction} config.includeAction - A filter function that decides if
 * the action is inserted into history.
 */

Note when using flattenState: all the normal state is still there (.past, .index, etc.) but the user's present state is just copied down a level.

Does this PR introduce a breaking change? (What changes might users need to make in their application due to this PR?)

This should not break any existing code

Other information:

Docs still have to be updated. However, the readme probably needs a little cleanup anyways.

Docs have been added

coveralls commented 4 years ago

Coverage Status

Coverage increased (+0.6%) to 98.305% when pulling 0261d1dc150fe7572a06d7d426cc7932ab88b5c7 on field-extensions into 931b2ecda73cfcf91a336d3ccffee1e744eb4483 on master.

nmay231 commented 4 years ago

@omnidan , I was able to add the docs, and this should be ready to merge as long as it looks good to you. It should definitely be squashed to not have all the annoying commits :smile:

One thing to note, I removed some of the extraneous types like IncludeAction and just typed the functions directly. For some reason, CombineFilters (the type) was exported so I put a deprecation flag explaining to just use typeof combineFilters (the function). No code should break though.

Cheers :+1: