WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.47k stars 4.18k forks source link

Passing data from parent blocks to its child blocks #9032

Closed ghost closed 6 years ago

ghost commented 6 years ago

Is there any ability to pass parent block attributes to its child blocks ? For example : Team members block should have child blocks (single team member) .. If I have some attributes at the parent block, how can the child blocks read these attributes?

aduth commented 6 years ago

If I have some attributes at the parent block, how can the child blocks read these attributes?

Can you be more specific about the type of information you'd need from the parent block in your example?

I'm inclined to think that the child blocks should operate independently, even if they're only contextually relevant in a particular parent block.

At worst, you always have the option to use wp.element.createContext to pass data down through a hierarchy of elements.

https://reactjs.org/docs/context.html

aduth commented 6 years ago

I'm going to proactively close this as "Won't Fix", given that this isn't something we'd want to encourage, and is technically otherwise possible to achieve through context APIs.

Closing doesn't mean discussion needs to end, or that it can't be reopened if valid use cases surface.

chrisvanpatten commented 6 years ago

You can also use select and dispatch to grab the parent block and manipulate its children, as another option. Quick example:

var child = select('core/editor').getBlocksByClientId(clientId)[0].innerBlocks[0]

// Update the child block's attributes
dispatch('core/editor').updateBlockAttributes(child.clientId, updatedAttributes)
ghost commented 6 years ago

Ok, I will explain my problem in more detail. I have the team members block that contains children blocks "single-team-member" Each child has a name and job title. What I need now is to make it easy for the user to edit the name color and font size as well as the job title color and font size. If the user has 20 team members, I don't think it is a good option to give him the ability to edit colors and font sizes from inside the child block because the colors and font sizes will be almost the same for all team members. He will have to repeat this process 20 times. Now, this should be done from the parent block .. when the user selects a specific color, all children should be styled at the same time with this color. that's why I needed the option to pass the parent attributes to the inner blocks.

ghost commented 6 years ago

But I think @chrisvanpatten solution is good. I just wondered how there isn't an option for the children blocks to access parent attributes easily.

chrisvanpatten commented 6 years ago

@muhammedmagdi For your use-case I'd just make it a setting on the parent block that applies a CSS class to the parent, and use CSS to adjust the children automatically. Something like .parent.has-background-color-red .child { background: red; }. Setting the attributes individually could be kind of fragile.

ghost commented 6 years ago

@chrisvanpatten This won't be a good solution because I will have to add all colors in the css .. If the user chooses a strange color like #48e854 , how should I include the name of this color. This isn't a practical solution. Massive css for every color option and won't work for all colors.

narendraj9 commented 6 years ago

Is it possible to access all the post's content as HTML or text from within a block? An example use case would be a custom block that shows the readability score of the current post.

I am posting my question here because I think I am trying to do something similar.

aduth commented 6 years ago

@narendraj9 You can use one of either getEditedPostAttribute with the content attribute, or the getBlocks function, respectively returning the HTML string or array of post blocks.

https://wordpress.org/gutenberg/handbook/data/data-core-editor/#geteditedpostattribute https://github.com/WordPress/gutenberg/blob/99beb15c0cc0af869b255412d7cbc00c6b958d4a/packages/editor/src/store/selectors.js#L525-L536

More information on using data:

https://wordpress.org/gutenberg/handbook/packages/packages-data/

(You'd use withSelect to subscribe to data in a component)

Note that retrieving HTML as a string can be a costly operation (potentially degrading performance if called frequently), since it requires serializing every block to HTML. In the context of the editor, it is recommended to work with the parsed block objects instead.

narendraj9 commented 6 years ago

Thanks a lot for this reference. I will try to implement my feature after going through the code. :)

d4mation commented 5 years ago

@narendraj9

You can pass Attributes from your Parent Block to your Child Block by using the template option in <InnerBlock>. This only works if you already know what the Children are to define them within your Template, but it worked well in my situation.

https://github.com/WordPress/gutenberg/tree/master/packages/editor/src/components/inner-blocks#template

Edit:

Although, I found this has a pretty big limitation. Any Class Names you add to the Children based on the Parent's Attributes are only added on initial creation of the Child. So this may not work in your case (It ended up not working in mine).

rogerlos commented 5 years ago

A huge use-case for this is having a child block in innerblocks which can show or hide fields depending on a decision the user makes in the parent container.

I'm building a section block which has an innerblocks area containing a clone of a widget I've used on a cajillion sites, highlighting content from a particular post. Different sections "types" show/hide different aspects of the "widget". Users can do this on the fly, choosing between (for example) showing the widget with image backgrounds and hiding the excerpts or displaying a text-only widget.

It would be ridiculous to have eight slightly different blocks which all do the same thing, and expecting the user to rebuild the section every time they wanted to look at a new look.

To be clear, I know I can do this in CSS for the display side, but on the edit side, if we're to stay true to the WYSIWYG model, the fields hidden on the front should be hidden on the back when the selected type dictates.

I may be old and moribund, but it seems crazy that a child element cannot know anything about its parent. I mean, it's sitting right there. If you have an example of how this might be done, using ES5, I'm all ears.

ghost commented 5 years ago

@d4mation the problem is that we can't pass any data directly from the parent to its children. What you have mentioned for using template option inside <InnerBlock> can't be used to pass data. It is only used to render specific child blocks inside the parent block

rogerlos commented 5 years ago

It seems to me that by invoking Innerblock, a developer is shouting "I NEED SOME CONTEXT"--that's what an Innerblock area is, a way to place blocks which require wrapping (and therefore context).

Because the Innerblock component has easy access to all of the attributes of the block invoking it, it therefore should be passing those attributes to its children automatically, perhaps by setting up a data store or whatever which can be simply accessed from a child block via something like props.parent.

Stateless is maybe lovely in the Facebook ecosystem, but building pages is literally about building a nested hierarchy, and the hoops needed to work around stateless makes it seem like a really interesting choice. In my particular world, I've been paid to build over 200 WordPress websites over the last [christ I'm old], and not a single one of those was or is a "blog"--all use WordPress as a content management system, and all have at least one template which invokes "widget-like" boxes featuring content, gathered within other boxes, which might be gathered within even more boxes. This is a very basic pattern.

Gutenberg ultimately will be a step forward, being less complex than other page-building systems out there like "elementor" and their kin. But there isn't a single client of mine who will find Gutenberg useful on launch day, given how their pages are typically assembled, and that seems like an "own goal" for a hyped feature. (I wish core dev, in all areas, had more folks who came from a "WordPress as CMS" background. I've never met a WordPress dev who makes their living from "bloggers", the actual "get paid to do WordPress" world is pretty much exclusively about using WP as a CMS.)

ghost commented 5 years ago

@rogerlos You are right. That's why I had created a pull request for this feature but it wasn't accepted because they think that there is no need for this feature.

fishstix81 commented 5 years ago

@aduth given that you are suggesting to use the context api in this way, do you have an example of how that might be used?

danielbachhuber commented 5 years ago

I've run into this issue too 😄

You can also use select and dispatch to grab the parent block and manipulate its children, as another option. Quick example:

This is a pretty horrible hack 😄 But it seems like the most realistic option right now.

aduth commented 5 years ago

I might imagine a minimal example using context to pass some generic value to look like:

const MyBlockContext = wp.element.createContext()

registerBlockType( 'my-plugin/my-outer-block', {
    edit() {
        return (
            <MyBlockContext.Provider value={ 10 /* or some other value */ }>
                <InnerBlocks />
            </MyBlockContext.Provider>
        );
    }
} );

registerBlockType( 'my-plugin/my-inner-block', {
    edit() {
        return (
            <MyBlockContext.Consumer>
                { ( value ) => (
                    <div>The value is: ${ value }</div>
                ) }
            </MyBlockContext.Consumer>
        );
    }
} );
sereganfsmw commented 5 years ago

@aduth , your suggestion of passing parameters from parent to child blocks works, but value is received for ‘edit’ only. ‘save’, however, receives empty value. The following function is being displayed while outputting to console: ƒ (){var e=at®;return Object(Z.createElement)(Z.RawHTML,null,e)}

Could you please advise us on how to receive the value for ‘save’? Thanks in advance!

ghost commented 5 years ago

@sereganfsmw , you can make an attribute and update this attribute with edit function then the save function will receive that attribute

sereganfsmw commented 5 years ago

@muhammedmagdi i din't understand how i update attribute in child blocks, if we only can received it for 'edit', can provide some example code ?

swinggraphics commented 5 years ago

@d4mation

You can pass Attributes from your Parent Block to your Child Block by using the template option in <InnerBlock>.

Tried this, but it doesn't work:

<InnerBlocks template={ [ [ 'rhm/toggle-card', { titleTag: 'h' + level } ] ] } allowedBlocks={ [ 'rhm/toggle-card' ] } />

I have used console logging to verify that level is set as desired at the time a child block is added. The child block uses the default for titleTag property setting under all circumstances.

RajkiranBagal commented 4 years ago

For those who are finding a solution to use parent block attributes in child block. You can do like below: Write below code in Child block.

const parentClientId = select( 'core/block-editor' ).getBlockHierarchyRootClientId( this.props.clientId ); //Pass Child's Client Id.

const parentAttributes = select('core/block-editor').getBlockAttributes( parentClientId ); //Pass the Parents CLient Id from above and get all Parent attributes

chvillanuevap commented 4 years ago

I would like to add my 2 cents to this. I'm using Gutenberg to build an HTML email template. I have a Social Links parent block which contains multiple Social Link children blocks. In the Social Link children, I display an image with a social media logo (Facebook, Instagram, etc.). I would like the user to be able to display a different image depending on an attribute of the parent block. For example, one option would be the classical Facebook logo with the white text on a blue background, and another would be the opposite with blue text on a white background. Since I'm building this for an email template, my CSS is extremely limited and SVG is not supported by most email providers. I can't use the styles property of registerBlockType. I have to actually change the HTML output and display the image inside an <img> tag for it to be compatible with email inboxes.

tousignant-christopher commented 4 years ago

I don't know how I got attached to this thread, but I got an e-mail about it. I must have accidentally hit notify during my travels.

I do want to throw in my input and that is that WordPress already has an API to do this. It is under the wp.data object and pretty much extends React Redux. This allows you to subscribe and dispatch events to a single set of data and any Gutenberg block would be able to subscribe to it and share/update its data.

Here is their documentation on this feature: https://developer.wordpress.org/block-editor/packages/packages-data/.

If you don't like that solution or are looking for something else, don't forget that Gutenberg is just a React App and you can use pure React as well. I would suggest taking a look at React's Context component. You can learn more here: https://reactjs.org/docs/context.html

cpiber commented 3 years ago

I've stumbled across this problem now as well. Sadly all the solutions here only work with the edit function. Did anyone find a solution to access these in save? Even https://developer.wordpress.org/block-editor/reference-guides/block-api/block-context/ isn't available.

Currently I have a little hack, in edit I retrieve the block attributes of the parent and if they are different save them after a timeout...

fukou commented 9 months ago

I've encountered a similar situation where I had to access data from a parent block to a child block. I have used the Block Context API for the custom block on Edit but it is not available on the Save as per the previous comment above.

Is there a way to achieve this functionality during the Save blocks as well? Despite searching across various platforms, I haven't found a suitable solution... 🥲


Sorry for pinging you @cpiber, I was wondering, what's your approach for this workaround? I'd like to know if you don't mind sharing 🙏🏻

cpiber commented 9 months ago

I'm trying to get the ID of my parent block. My hack is that I save a separate copy of the ID in the child block, and on every edit I query the parent's ID and if they are different, store it:

        const { id } = useSelect(select => {
            const { getBlockRootClientId, getBlockAttributes } = select('core/block-editor');
            const rootId = getBlockRootClientId(clientId);
            return getBlockAttributes(rootId);
        }, [clientId]);
        if (id !== attributes.id) setTimeout(setAttributes, 0, { id });

Then in save I can just use the block's ID

fukou commented 9 months ago

I see. Thank you for sharing @cpiber, I really appreciate it! 🙏🏻


So this is a general question but I'm not sure if this is possible on save, but my use case is as follows. I am currently building a CTA block where the block can have multiple children blocks (think about the columns and column block, it's similar):

/**
 * WordPress dependencies
 */
import { useSelect } from "@wordpress/data";

/**
 * Internal dependencies
 */
import { CTAItemInterface } from "./interface";

import CTARegular from "./templates/cta-regular";
import CTATile from "./templates/cta-tile";

export default function save(props: CTAItemInterface): JSX.Element {
  const { attributes } = props;
  const { columnWidth } = attributes;

  // Select all parent blocks
  const parentBlocks = useSelect((select) => select('core/block-editor').getBlockParents(props.clientId), []);

  // Get the attributes from the blocks
  const parentBlockAttr = useSelect((select) =>
    select('core/block-editor').getBlockAttributes(parentBlocks[parentBlocks.length - 1]),
    []
  );

  // Grab the attributes from the parent
  const { listGap, listLayout } = parentBlockAttr;

  /**
   * We want to make sure that the gap values are also applied to child element as well
   * By recalculating the initial columnWidth with the gap
   * So it won't wrap to other rows
   */
  const generatedValue = `calc(${columnWidth} - ${listGap}px)`;
  const blockProps = useBlockProps.save({
    style: {
      flexBasis: generatedValue,
    },
  });

  return (
    <>
      <div {...blockProps}>
        {/* Check which layout needs to be rendered */}

        {/* Regular layout */}
        {listLayout === "list-regular" && (
          <CTARegular {...props} />
        )}

        {/* Cover layout */}
        {listLayout === "list-cover" && (
          <CTATile {...props} />
        )}
      </div>
    </>
  );
}

Using the useSelect hook on the save doesn't seem to work, so I don't know the appropriate approach for this 🥲

If anyone here has more insight, it would be very much appreciated!

cpiber commented 9 months ago

Yes, I noticed this as well, which is why I did my workaround of loading the data in edit and storing it on the child