Skip to content

Instantly share code, notes, and snippets.

@rmorse
Last active February 20, 2023 05:36
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rmorse/de3df5ac9ff751c4c0fc6234397b7930 to your computer and use it in GitHub Desktop.
Save rmorse/de3df5ac9ff751c4c0fc6234397b7930 to your computer and use it in GitHub Desktop.
Generate unique IDs (as attributes) for blocks - ensure that duplicating a block will generate a new ID
/**
* WordPress dependencies
*/
import { useSelect, dispatch } from '@wordpress/data';
import { useLayoutEffect } from '@wordpress/element';
import './store';
const storeName = 'plugin-name/block';
function Edit( { attributes, setAttributes, clientId } ) {
// Get the stored attribute field ID.
const attributeFieldId = attributes.fieldId;
// Get the copy of the stored field ID in our custom store.
const fieldId = useSelect( ( select ) => {
return select( storeName ).getClientFieldId( clientId );
}, [ attributeFieldId ] );
// If the attribute field ID has a value, register that in our store so it knows the value.
useLayoutEffect( () => {
if ( attributeFieldId !== '' ) {
dispatch( storeName ).setFieldId( clientId, attributeFieldId );
}
}, [ attributeFieldId ] );
// If there is no value in the store or in the attribute, create a new field ID.
// Or if there is a field ID in the store (but not attribute), then copy that into
// the attribute.
useLayoutEffect( () => {
if ( attributeFieldId === '' & fieldId === null ) {
dispatch( storeName ).createFieldId( clientId, attributes );
} else if ( attributeFieldId === '' & fieldId !== null ) {
setAttributes( { fieldId } );
}
}, [ attributeFieldId, fieldId ] );
// To handle duplicates, we need to check if a reset is needed, this is handled in
// the store.
const shouldResetFieldId = useSelect( ( select ) => {
return select( storeName ).shouldResetFieldId( clientId );
}, [ attributeFieldId, fieldId ] );
// Now wait for the reset to be true before unsettting the attribute field ID and
// the store field ID.
useLayoutEffect( () => {
if ( shouldResetFieldId ) {
setAttributes( { fieldId: '' } );
dispatch( storeName ).setFieldId( clientId, '' );
}
}, [ shouldResetFieldId ] );
return (
<>
Block Field ID: { attributeFieldId }
</>
);
}
export default Edit;
/**
* WordPress dependencies
*/
import { createReduxStore, register } from '@wordpress/data';
import apiFetch from '@wordpress/api-fetch';
const actions = {
* createFieldId( clientId, attributes ) {
// Create new item on the server and return its ID
// TODO - insert your own ID generation logic here
const args = { attributes };
const path = `/plugin-name/v1/save`;
const result = yield actions.postToAPI( path, args );
// yield setEntityIsLoading( section, id, false );
if ( result.code === 'success' ) {
const resultId = result.id;
return actions.setFieldId( clientId, resultId );
}
},
setFieldId( clientId, fieldId ) {
return {
type: 'SET_FIELD_ID',
clientId,
fieldId,
};
},
postToAPI( path, data ) {
return {
type: 'POST_TO_API',
path,
data,
};
},
};
const DEFAULT_STATE = {
clientFieldIds: {},
clientsOrdered: [],
};
export const defaults = DEFAULT_STATE;
export const blockStore = createReduxStore(
'plugin-name/block',
{
reducer( state = DEFAULT_STATE, action ) {
switch ( action.type ) {
case 'SET_FIELD_ID': {
const clientsOrdered = [ ...state.clientsOrdered ];
if ( clientsOrdered.indexOf( action.clientId ) === -1 ) {
clientsOrdered.push( action.clientId );
}
return {
...state,
clientFieldIds: {
...state.clientFieldIds,
[ action.clientId ]: action.fieldId,
},
clientsOrdered,
};
}
}
return state;
},
actions,
selectors: {
getClientFieldId( state, clientId ) {
if ( state.clientFieldIds ) {
if ( state.clientFieldIds[ clientId ] ) {
return state.clientFieldIds[ clientId ];
}
}
return null;
},
getClientFieldIds( state ) {
return state.clientFieldIds;
},
getClientsOrdered( state ) {
return state.clientsOrdered;
},
shouldResetFieldId( state, clientId ) {
/**
* To know if we need to reset a fieldId attribute, we need to compare all the
* clientIds associated fieldIds, and look for duplicates. No two clientIds
* should have the same fieldId.
*/
// First check to see if the current clientId has stored fieldId
const clientFieldId = state.clientFieldIds[ clientId ];
// If no ID, don't trigger the reset and exit early.
if ( clientFieldId === null ) {
return false;
}
// Now we need to compare all the clientIds to see if any have the same fieldId
const { clientFieldIds, clientsOrdered } = state;
let shouldReset = false;
for ( const clientIdRef of clientsOrdered ) {
// If we find a match we might need to reset.
if ( clientFieldId === clientFieldIds[ clientIdRef ] ) {
// If the first match is not current client ID, we need to reset.
if ( clientId !== clientIdRef ) {
shouldReset = true;
}
break;
}
}
return shouldReset;
},
},
controls: {
POST_TO_API( action ) {
return apiFetch( {
path: action.path,
method: 'POST',
data: action.data,
} );
},
},
resolvers: {},
}
);
register( blockStore );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment