Skip to content

Commit

Permalink
Add reset state actions to our stores. (#998)
Browse files Browse the repository at this point in the history
## Problem this Pull Request solves

While working on the demo app (#988) and the Barcode Scanner app (eventespresso/eea-barcode-scanner#7) I realized that there will be need for some sort of reset state action for our stores that allows dispatches to restore state to its default shape.  For instance with the barcode scanner app the user might scan a new registration for checkin that we'll want to reset all the tracked related registrations etc in the state.

Note: a [pull I did for GB](WordPress/gutenberg#14225) recently that adds more sledgehammer like control over the resolution cache in the `core/data` store.  It will make it easier for resetting that state in this pull.  The downside is it will be tied to a version of WordPress so I can only use it IF the functions exist.
  • Loading branch information
nerrad authored Mar 12, 2019
1 parent 272bfe5 commit 3b87d2d
Show file tree
Hide file tree
Showing 17 changed files with 1,192 additions and 40 deletions.
2 changes: 1 addition & 1 deletion assets/dist/build-manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"components.css": "ee-components.997177945b17ee98a639.dist.css",
"components.js": "ee-components.cf0aaacfef0cb46b08ec.dist.js",
"data-stores.js": "ee-data-stores.fc604083647264add736.dist.js",
"data-stores.js": "ee-data-stores.b9c742685b104fe37411.dist.js",
"editor-hocs.js": "ee-editor-hocs.15a813aac7a51b5473cb.dist.js",
"eejs.js": "ee-eejs.4bd48b02f85a875e3121.dist.js",
"eventespresso-core-blocks-frontend.css": "ee-eventespresso-core-blocks-frontend.f0ea9ad96d720bc8dc9b.dist.css",
Expand Down

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion assets/src/data/eventespresso/core/actions/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ const relations = {
RECEIVE_UPDATED_ENTITY_ID_FOR_RELATIONS:
'RECEIVE_UPDATED_ENTITY_ID_FOR_RELATIONS',
};
const resets = {
RESET_ALL_STATE: 'RESET_ALL_STATE',
RESET_STATE_FOR_MODEL: 'RESET_STATE_FOR_MODEL',
RESET_ALL_MODEL_SPECIFIC: 'RESET_ALL_MODEL_SPECIFIC_STATE',
RESET_MODEL_SPECIFIC_FOR_SELECTOR: 'RESET_MODEL_SPECIFIC_FOR_SELECTOR',
};

const modelSpecific = {
RECEIVE_SELECTOR_VALUE: 'RECEIVE_SELECTOR_VALUE',
};

export const ACTION_TYPES = { entities, relations, modelSpecific };
export const ACTION_TYPES = { entities, relations, modelSpecific, resets };
238 changes: 238 additions & 0 deletions assets/src/data/eventespresso/core/actions/reset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/**
* External imports
*/
import {
singularModelName,
pluralModelName,
} from '@eventespresso/model';
import { some, keys } from 'lodash';
import { isModelEntityOfModel } from '@eventespresso/validators';
import { select as dataSelect } from '@wordpress/data';

/**
* Internal imports
*/
import { dispatch, select } from '../../base-controls';
import { ACTION_TYPES } from './action-types';
import { REDUCER_KEY } from '../constants';
import * as modelSpecificSelectors from '../model/model-selectors-index';

const { resets: types } = ACTION_TYPES;

/**
* Resets the entire state to its default for the store.
*/
export function* resetAllState() {
// action for resetting the entire state.
yield {
type: types.RESET_ALL_STATE,
};

// get resolvers from core/data and dispatch invalidation of each resolver.
const resolvers = yield select(
'core/data',
'getCachedResolvers',
REDUCER_KEY
);

if ( invalidateActionsAvailable() ) {
yield dispatch(
'core/data',
'invalidateResolutionForStore',
REDUCER_KEY,
);
return;
}

// dispatch invalidation of the cached resolvers
for ( const selector in resolvers ) {
for ( const entry of resolvers[ selector ]._map ) {
yield dispatch(
'core/data',
'invalidateResolution',
REDUCER_KEY,
selector,
entry[ 0 ]
);
}
}
}

/**
* Resets all state related to the given modelName
*
* Note: This does not reset any state in the modelSpecific tree as there is no
* way to know what applies to the current model.
*
* @param {string} modelName
*/
export function* resetStateForModel( modelName ) {
yield {
type: types.RESET_STATE_FOR_MODEL,
modelName,
};

// get resolvers from core/data
const resolvers = yield select(
'core/data',
'getCachedResolvers',
REDUCER_KEY
);

// dispatch invalidation of the cached resolvers for any resolver that
// has a variation of modelName in the selector name or in the args for the
// cached resolver.
for ( const selector in resolvers ) {
for ( const entry of resolvers[ selector ]._map ) {
if (
(
modelNameInSelector( selector, modelName ) ||
modelNameInArgs( entry[ 0 ], modelName )
) &&
! selectorIsModelSpecific( selector )
) {
yield dispatch(
'core/data',
'invalidateResolution',
REDUCER_KEY,
selector,
entry[ 0 ],
);
}
}
}
}

/**
* Helper for determining whether the given modelName is found in the given
* selectorName.
*
* @param {string} selectorName
* @param {string} modelName
*
* @return {boolean} True means it is present, false means it isn't
*/
const modelNameInSelector = ( selectorName, modelName ) => {
const singularName = singularModelName( modelName );
const pluralName = pluralModelName( modelName );
selectorName = selectorName.toLowerCase();
return selectorName.indexOf( singularName ) > -1 ||
selectorName.indexOf( pluralName ) > -1;
};

/**
* Helper for determining whether the given modelName is found in the given
* set of args.
*
* This also considers if any of the args are an instance of BaseEntity and
* if that BaseEntity instance is for the given model.
*
* @param {Array} args
* @param {string} modelName
*
* @return {boolean} True means it is present, false means it isn't.
*/
const modelNameInArgs = ( args, modelName ) => {
const singularName = singularModelName( modelName );
const pluralName = pluralModelName( modelName );
const hasModelName = args.indexOf( singularName ) > -1 ||
args.indexOf( pluralName ) > -1;
if ( hasModelName ) {
return true;
}

// it's possible one of the args is an instance of BaseEntity. If so,
// then let's compare against the modelName on the entity instance.
return some(
args,
( arg ) => {
return isModelEntityOfModel( arg, singularName ) ||
isModelEntityOfModel( arg, pluralName );
}
);
};

/**
* For the given selector name and (optional) selectorsToInvalidate, this
* returns whether the selectorName is a match for the selectors to invalidate.
*
* @param {string} selectorName
* @param {Array|null?} selectorsToInvalidate If null, then the match array will
* be obtained from the registered modelSpecificSelectors imported for the
* module
*
* @return {boolean} True means there is a match, false means there is not.
*/
const selectorIsModelSpecific = (
selectorName,
selectorsToInvalidate = null
) => {
selectorsToInvalidate = selectorsToInvalidate === null ?
keys( modelSpecificSelectors ) :
selectorsToInvalidate;
return selectorsToInvalidate.indexOf( selectorName ) > -1;
};

/**
* Resets all model specific state.
*/
export function* resetAllModelSpecific() {
yield {
type: types.RESET_ALL_MODEL_SPECIFIC,
};

// get resolvers
const resolvers = yield select(
'core/data',
'getCachedResolvers',
REDUCER_KEY
);

const selectorsToInvalidate = keys( modelSpecificSelectors );

// dispatch invalidation of the cached resolvers for model specific selector
for ( const selector in resolvers ) {
for ( const entry of resolvers[ selector ]._map ) {
if ( selectorIsModelSpecific( selector, selectorsToInvalidate ) ) {
yield dispatch(
'core/data',
'invalidateResolution',
REDUCER_KEY,
selector,
entry[ 0 ],
);
}
}
}
}

/**
* Resets all state for a given model specific selector and its args
*
* @param {string} selectorName
* @param {Array} args
*/
export function* resetModelSpecificForSelector( selectorName, ...args ) {
yield {
type: types.RESET_MODEL_SPECIFIC_FOR_SELECTOR,
selectorName,
args,
};

yield dispatch(
'core/data',
'invalidateResolution',
REDUCER_KEY,
selectorName,
args,
);
}

/**
* Helper for determining if actions are available in the `core/data` package.
*
* @return {boolean} True means additional invalidation actions available.
*/
const invalidateActionsAvailable = () => {
return dataSelect( 'core/data' ).invalidateResolutionForStore !== undefined;
};
Loading

0 comments on commit 3b87d2d

Please sign in to comment.