Skip to content
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

How do I get the clientId of a block? #25195

Open
TJBriggs opened this issue Sep 9, 2020 · 15 comments
Open

How do I get the clientId of a block? #25195

TJBriggs opened this issue Sep 9, 2020 · 15 comments
Assignees
Labels
[Type] Developer Documentation Documentation for developers

Comments

@TJBriggs
Copy link

TJBriggs commented Sep 9, 2020

I have been scouring the internet for documentation on this. The WordPress documentation focuses on all the methods you can run if you have the clientId, but I can't figure our how to get that ID dynamically during the edit or save portion of my block. I'm trying to use the following code to access all my innerblock data/attributes but I need the clientId first:

wp.data.select('core/block-editor').getBlocks(clientId);

Can anyone point me in the right direction on this?

@talldan
Copy link
Contributor

talldan commented Sep 10, 2020

@TJBriggs The clientId is passed as a prop to the edit component, e.g.:

function edit( { clientId, attributes } ) {

I don't think it's present in the save component because the clientId isn't persistent between editor sessions, it's regenerated for each block when the editor loads. The markup produced by save should depend on the block attributes only so that it's consistent between editor sessions.

I'll add the documentation label to this issue so that we treat this as a request to update the docs, as I think this is something that should be mentioned.

@talldan talldan added the [Type] Developer Documentation Documentation for developers label Sep 10, 2020
@bobbingwide
Copy link
Contributor

Since this issue is still open I'd like to add my question.
I'm writing a Chart block, which can be used mutiple times in a post.
The block will run Chart.js to display the chart in the editor.

  • I need a unique ID for each chart in the post.
  • I tried to use withInstanceId to obtain a unique ID.
  • It worked for edit() but I not for save().
  • So now I'm trying to get the block's clientId.
  • And discovered this issue.

What method should I use?

@talldan
Copy link
Contributor

talldan commented Jan 14, 2021

@bobbingwide Take a look at this issue - #17246.

I think the discussed react hook is still the best option for static blocks - #17246 (comment)

The other option is to use a dynamic block, but granted you may not be able to that if you're using a JS library.

@ryanwelcher
Copy link
Contributor

We should document the entire props object that is passed to both edit and save in https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/

@Julianoe
Copy link

This is an oldie but I stumbled upon it while searching how to have an Id associated to a block.
The fact that this is not documented on the block edit save is indeed problematic. Had to dig stackoverflows and github issues just to know the existence of clientId
Starting to work a little again with blocks after 2 years and a lot has changed .... hard to follow and to learn.. (which is deplored here and I agree, the css tricks article I used to get back on page)

@roborourke
Copy link
Contributor

If anyone needs it here's an example of how you can persist a block's generated clientId, although it should be noted this can't be used for any dynamic wp.data functions like getBlocks(). This is only useful if you need to save a unique identifier to the block's attributes which is pretty rare.

function Edit( { attributes, setAttributes, clientId } ) {

   // Set the uniqueId from clientId only if not already set.
   useEffect( () => {
      if ( ! attributes.uniqueId ) {
          setAttributes( { uniqueId: clientId } );
      }
   }, [ attributes.uniqueId, clientId ] );

   // ...
}

// ... in the save function
function save( { attributes } ) {
   return <div data-uuid={ attributes.uniqueId }></div>
}

@peterbode1989
Copy link

If anyone needs it here's an example of how you can persist a block's generated clientId, although it should be noted this can't be used for any dynamic wp.data functions like getBlocks(). This is only useful if you need to save a unique identifier to the block's attributes which is pretty rare.

function Edit( { attributes, setAttributes, clientId } ) {

   // Set the uniqueId from clientId only if not already set.
   useEffect( () => {
      if ( ! attributes.uniqueId ) {
          setAttributes( { uniqueId: clientId } );
      }
   }, [ attributes.uniqueId, clientId ] );

   // ...
}

// ... in the save function
function save( { attributes } ) {
   return <div data-uuid={ attributes.uniqueId }></div>
}

I was using something similair like this. A good thing to note is, that when you duplicate post-X. The copied results have the same uniqueId. What results in a duplicate IDs (front-end).

@hadamlenz
Copy link

setting an attribute in a useEffect makes the save button light up... which means you have to save the page.. which means the post is considered dirty, and some people use that as an indicator that something has happened. It's probably bad practice to set an attribute and create a condition where the editor always has to be saved. In my mind this should be avoided.... but then, how can we get the client Id in save(js) or render(php)???

@peterbode1989
Copy link

setting an attribute in a useEffect makes the save button light up... which means you have to save the page.. which means the post is considered dirty, and some people use that as an indicator that something has happened. It's probably bad practice to set an attribute and create a condition where the editor always has to be saved. In my mind this should be avoided.... but then, how can we get the client Id in save(js) or render(php)???

useEffect only triggers when you initialize/ add the block for the first time. Adding a block makes the save option enabled.
If you set the param correctly, the second time you load the page the save option should be enabled.

This is the code I'm currently using in production and works as expected.

const { attributes, setAttributes, clientId } = props
const { blockId } = attributes

React.useEffect(() => {
// Make sure the block gets a unique ID assigned
if (!blockId || isBlockIdReserved(blockId, clientId)) {
setAttributes({ blockId: clientId })
}
}, [])

What is isBlockIdReserved?
This function checks all other blocks for duplicate blockIDs (this is needed when duplication has been used by the client)

export function isBlockIdReserved(blockId, clientId) {
const blocksClientIds = select('core/block-editor').getClientIdsWithDescendants();
return blocksClientIds.some((_clientId) => {
const { blockId: _blockId } = select('core/block-editor').getBlockAttributes(_clientId);
return clientId !== _clientId && blockId === _blockId;
});
};

How does it work?
On init

  1. Page gets loaded (blank editor)
  2. Client adds the block
  3. Block init triggers: and sets the blockId attribute assigned to the selected block (this makes saving the page possible)
  4. The block is saved with a blockId attribute

On edit

  1. Page gets loaded (with blocks)
  2. Iteration occurs and loops through all the appended blocks for the given page
  3. On INIT for the block the blockId is checked for if it's SET
  4. IF the blockId has a blank value, the blockId is set to the current clientID (this enabled the save option)
  5. If the blockId isn't blank, the useEffect trigger doesn't resolve and the save buttons remains disabled

Additional notes
I registered the attribute: blockId the following way for my blocks (located inside block.json)
"attributes": {
"blockId": {
"type": "string"
}
}

Settings an default value for the attribute: blockId, makes it work unreliable. And the easiest way for me was the remove the default value (undefined, NULL, false all gave strange attrifact while working with the block).

ClientID inside save/render instances?
These don't exists. The clientId is a identifier used by the editor to give all blocks a unique ID.
By using the blockId attribute (like shown above), you can get the blockId from the attributes.
In my example I set the blockId as an actual ID for the rendered block (this can be used on the front end).

export default function save({ attributes }) {
const { blockId } = attributes
const blockProps = useBlockProps.save()
return (
<div id={block-${blockId}} {...blockProps}>
<InnerBlocks.Content />
</div>
);
}

For render.php it works the same:
echo "<div " . get_block_wrapper_attributes([
"id" => "block-" . $attributes['blockId']
]) . ">";
echo $block_content;
echo "</div>";

I hope this helps you a bit with your problem.

@peterbode1989
Copy link

peterbode1989 commented Dec 20, 2024

I just had a idea. Maybe this is what is happing for you.

Just did a "standalone" test with my code (standalone plugin that adds the blocks to the environment).
Created an entire new environment (twentytwentyfour) with only my blocks-plugin added / enabled. And the save function stays disabled.

After reloading the page, with the custom block with blockId. The save option is disabled.


After I add Yoast SEO and enable it. The save function always stays enabled ( this is something I noticed while building my plugin ).
So are you sure it's your code, and not just Yoast doing it?

@hadamlenz
Copy link

anytime a setAttribute is fired the save button lights up. it depends on the conditionals... in your example the blockId is already set in the saved attributes when the page loads again and so it doesn't light up on editor init. but if you needed to keep that clientID up to date on each editor reload it would fire on each editor reload. Without if(!blockId...

You even say "although it should be noted this can't be used for any dynamic wp.data functions like getBlocks()". which is the heart of my issue.

@peterbode1989
Copy link

anytime a setAttribute is fired the save button lights up. it depends on the conditionals... in your example the blockId is already set in the saved attributes when the page loads again and so it doesn't light up on editor init. but if you needed to keep that clientID up to date on each editor reload it would fire on each editor reload. Without if(!blockId...

You even say "although it should be noted this can't be used for any dynamic wp.data functions like getBlocks()". which is the heart of my issue.

I never said "dynamic wp.data functions like", this was someone else and I quoted his comment and gave a reaction too this statement. Not that it matters much.
Can you tell me more about the thing you are exactly trying to do? Maybe this can help me help you.


Also did you try to get the block with the stored blockId, this doens't work because the clientId changes constantly. Every page load new clientIDs are generated for the given blocks. Not sure if this helps a bit, but this is just something that came to my mind.

@hadamlenz
Copy link

I need to save the clientId to some block attributes whenever the editor starts up. I'd like to make it available via context, but context seems to require you to save an attribute and since the clientID does change on every reload that attribute needs to be saved on every reload of the editor. Setting context in a react way doesn't work #50633 . I believe it's not possible, exploring other methods.

@peterbode1989
Copy link

I need to save the clientId to some block attributes whenever the editor starts up. I'd like to make it available via context, but context seems to require you to save an attribute and since the clientID does change on every reload that attribute needs to be saved on every reload of the editor. Setting context in a react way doesn't work #50633 . I believe it's not possible, exploring other methods.

This is correct, the clientID changes every cycle. And storing it for later use is a "work-around". But this value doesn't help you get any block information, it's more for your own use.

@Mamaduka
Copy link
Member

What's the remaining problem here? Can we close this issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Developer Documentation Documentation for developers
Projects
None yet
Development

No branches or pull requests

10 participants