-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Explore easier ways to declare and use html ids within blocks #17246
Comments
Correct me if I'm wrong, but I think |
@ZebulanStanphill It's pretty intricate the deeper you look into it. The issue with I think a UUID is the best bet. The clientID at the moment is the closest thing as that's unique for blocks, but having to use
☝️ That's not too bad, it handles duplicated blocks, but it still results in existing blocks having new ids set again when loading a post, given blocks get new client ids. |
Notably, if you did save a UUID to attributes, and used the attributes as the source of truth, then duplicating that block would result in the duplicate using the same id. Because of that, I can't think of a better solution than the usage of |
I think you're right on the money here. I've been fighting this issue all day. I've tried generating an ID when the block is constructed and saving this ID in attributes but there currently is no way to detect when a block is duplicated and purge this id field / regenerate. From looking at similar posts across the community this appears to be an oversight from the Gutenburg team. There appears to be some expectation that the output of a block save function can be constructed entirely from attributes of that same block but the function of duplication in this mindset completely destroys the idea of any form of uniqueness for a block. I've seen lots of people solve this issue by loading the client id of a block to its attributes in the edit() function and then accessing them again in the save() function. Ignoring the fact that you're updating state during a render there seems to be some doubt among the community that a blocks clientId can't change. So this solution is very much up in the air. Personally, I feel that Gutenberg should extend their list of actions and filters to allow people to produce their own solutions. Pre-render attribute filters and an "on-block-duplicated" hook would've provided a means to solve this issue easily. Alternatively, Gutenberg could update the attribute properties to allow you to mark attributes as non-duplicatable and also allow callbacks as default values for attributes as follows. var attributes = {
blockId: {
type: 'string',
duplicatable: false,
default: myGenerateIdCallback
},
} |
There's also a discussion/proposal at #32604 about unique ids (though not specifically html ids). |
I also came up against this problem and managed to solve in a round about way. Essentially, I'm generating an ID on the server, and assigning it to a block, but how the ID is generated shouldn't really matter. With a bunch of effects and a custom store, I'm then tracking client IDs against their unique ID (I'm calling this field ID) and if I find a duplicate field ID amongst any of the blocks (client IDs), I'm triggering a reset on the duplicate block (unsetting the field ID) so the process starts again and it gets assigned a fresh field ID. It seems like a lot of work to ensure unique IDs but I think it solves the duplicate issue... My block code looks like: /**
* 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; Theres also some logic in the custom store, I've put the whole lot in a gist here: Also just found this which should add support for attributes that shouldn't be duplicated: #34750 |
I don't think clientId is suitable for identification due it's nature of changing, spend hours figuring that out. What we did is generating an ID in the block on create / init and storing it in the attributes and in a class as suffix. Then we were able to check in the block init for duplicates in the editor dom just by doing a .querySelectorAll. For document you might wanna check for the correct root:
This solution is relatively stable combined with a random ID generator. Our IDs look like this: There is still a chance of getting duplicates if you combine block content of two or more post types (like pages in sidebars or something like that), but with random IDs the chance is relatively low and duplicates in the same page get replaced on creation. |
source: https://webdevstudios.com/2020/08/04/frontend-editing-gutenberg-blocks-1/ |
This will just check for an empty "id" attribute and use the clientId (which is not static) as value. This will not work very well if you need a static ID e.g. generating inline CSS for the specific block (for custom block controls). Even if you keep the id in the block attributes, this eventually will generate duplicates. Besides that, I don't know if attributes and clientId is already available on the very first render. IMHO |
Yeah, you are right, tried yesterday and had issues because it was not available on the very first rendering. I've removed the conditional and it works but I'm worried about duplicates. I tried to use uuid but It would loop infinitely, I clearly didn't know what I was doing... I've also used $id = { ...blockProps }.id; before, is that bad? In the end, I only have workarounds for this, wish I had a better solution. P.S. I don't need it to be static. |
Sounds interesting, would be nice to see it in context, like in a repo, to see how you're generating and storing the ids and whatnot. |
In our Beta version (which is around 10 months old), we utilized Higher Order Component to inject custom block controls and settings into standard blocks. At the time, this approach allowed us to implement some custom features. However, given the recent updates, we acknowledge that there might be alternate, more suitable approaches available. One challenge we face is generating unique identifiers that we can use as class selectors in the custom CSS. To do this, we've created our own 'static' IDs to ensure absolute uniqueness. From our helper.js:
Simple check after mount:
|
Sweet @dennisheiden will need to experiment with it. Thanks! |
This issue is more than 3 years old and as someone that does not work 100% of time on blocks but needs some occasionally I can't find any recommendation or documentation about the proper way to do this. The use case here is setting "aria-controls=" and "aria-labelledby=" on some sort of tabs blocks that collapses content, and styling, of course. Do you have any basic in context example that could be use by people trying to implement this? Any advice on the current state of the art on that matter would be very welcome. |
You can manipulate blocks in two ways: 1) Settings Injection (see my awnser somewhere above) or 2) at the output side (Filter: "render_block"). |
The filter for render_block should be avoided for attribute-injection. I tried this for the attribute ID. What did I do: Made a blank block, with clientId as saved-attribute. Then in the render_block set this ID to the dom. Copy it 1000x times and load the page. You'll see a increase in page load time. For one block: yes, multiple NO! |
Why would you see an increase in page load time if you do this server side? Our way of injection an unique id as class (sv100-premium-block-core-MGJlMDUyNWM1) :
But I bet there is a much smarter way now, our code is old and we never checked again, because it works. |
Everytime you request a page, the code runs. So that adds to the time before the server gives a page response. And by just saying let the server/cache handle it, is in my opinion not a perfect solution. Since 90% of all Wordpress installs are done with shared-hosting. Currently I haven't found a workaround for the problem. |
I understand doing stuff on runtime is not "perfect". That's negated by using cache - code is only executed once when the cache gets filled (hosting solution doesn't really matter IMHO). I also doubt the impact is significant. We've had no issues so far doing this un-cached on complex pages (the class is added to every block you insert). It could be slightly improved maybe by using different replacer functions, etc. (But I open to performance / test data if you have some.) Back to unique IDs:
|
Currently (still need to do a lot of testen), got a somewhat working "solution". I modified the ID checking functions (this idea /method was mentioned above):
Edit.js contains:
Save.js contains: |
I agree, for a personal / closed project this would be fine. But not for a production-application.
True, this was/is very unstable. And you mean triggering Block Recovery mode?
This is somewhat possible. If the value is immutable, then I should work (currently trying/testen this with setting a id on the save-hook. Also, this is what I did first. Just update the blockID with the value of clientID every page-load (back-end). This resulted in having the save option enabled. This was in my opinion not a good solution. Since the IDs of the front-end blocks constantly change. |
Interesting input, might check that on a custom block in some time. Yes clientID is not static. Generating your own IDs is necessary and checking against duplicates. BUT, there might be some more issues:
Or maybe just run with the clientID like Gutenberg does it for inline styles and refresh the ID on save? |
Is your feature request related to a problem? Please describe.
Sometimes when implementing block edit and save components, a unique id is required. An example could be for associating a label with an input:
I needed to do this on #15554 and found it surprisingly difficult—it might be that I'm missing a much easier way to do this, I'm open to suggestions.
The option I went with there was to declare an attribute that sources the id, and also uses the block's clientId to build a default uniqueId in the
edit
component.This real issue is that this had to be set as an attribute using
setAttributes
. For many use cases the only option might be to set this attribute when the component first renders, which would be less than ideal as it'd trigger an immediate re-render.Describe the solution you'd like
A way to declare a unique id in attributes could be an option:
This would ensure the attribute is available at first render and stays consistent before the block is saved.
The downside for me is that 'uniqueId' isn't really a JavaScript type. Perhaps there might be other options for declaring this aspect of the attribute.
Describe alternatives you've considered
Exposing the clientId in the save function could be another option to make this easier. That would mean
setAttributes
wouldn't need to be used as something like this would work:My main problem with this is that it's using
clientId
for something that it wasn't intended to do.The text was updated successfully, but these errors were encountered: