WordPress / gutenberg

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

editPost does not work when trying to update a single meta object with multiple values. #25668

Closed ThatStevensGuy closed 3 years ago

ThatStevensGuy commented 4 years ago

Describe the bug I have existing post meta stored as serialized arrays. In order to migrate to Gutenberg I need to be able to save this meta reliably in the old format. There are far too many posts to update to a new data structure.

As per this article. I have defined my schema, the existing meta displays correctly in the Rest API and in Gutenberg. I can add new meta to a new post fine. However, when I edit the meta, the TextControl (or any input) appears to be 'locked' as if the value does not update.

Note: I am able to make this work fine using the 'prepare_callback' and 'sanitize_callback' JSON method. But I was hoping to use meta schema, as that method throws a PHP notice about an array to string conversion.

It seems to me that Gutenberg cannot see that my object has updated with this data structure?

Meta registration:

register_post_meta( 
    'supporters',  
    'our_supporters',
    [
        'single' => true,
        'type' => 'object',
        'show_in_rest' => [
            'schema' => [
                'type' => 'object',
                'properties' => [
                    'supporter' => [
                        'type' => 'object',
                        'properties' => [
                            'first_name' => [
                                'type' => 'string'
                            ],
                            'last_name' => [
                                'type' => 'string'
                            ],
                            'phone' => [
                                'type' => 'string'
                            ],
                            'mobile' => [
                                'type' => 'string'
                            ],
                            'website' => [
                                'type' => 'string'
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ]
);

Save meta:

updateSupporter(newSupporter) {
    editPost({
        meta: {
            our_supporters: {
                supporter: newSupporter
            }
        }
    });
},

To reproduce Create a simple component and register post meta as above. Save meta, then try and edit the saved meta...

Expected behavior It should let me edit saved meta values!

Editor version (please complete the following information):

Desktop (please complete the following information):

ThatStevensGuy commented 4 years ago

After some more testing. Most evidence appears to show that Gutenberg/React is not detecting changes to an object inside an object.

If I append a timestamp to the top level object, just a string with a timestamp that updates. Then pass that along to my component everything works as expected. It's a bit of a hack though.

I can only assume it doesn't check inside sub objects for performance reasons?

editPost({
    meta: {
        our_supporters: {
            supporter: newSupporter,
            meta_modified: new Date().toISOString()
        }
    }
});
Mamaduka commented 3 years ago

Hi, @ThatStevensGuy

I couldn't reproduce the issue using the current version of WordPress (5.7) or the Gutenberg plugin (10.2.1).

I've registered post meta using your snippet; the only difference is that I used post as the post type. Then dispatched editPost actions by pasting the following code in the console:

var newSupporter = {
    first_name: 'George',
    last_name: 'Mamadashvili',
    phone: '555555555',
    mobile: '555555555',
    website: 'github.com',
};

wp.data.dispatch('core/editor').editPost({
    meta: {
        our_supporters: {
            supporter: newSupporter
        }
    }
});

var meta = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'meta' );

I was also able to modify metadata by changing values in the newSupporter variable.

Mamaduka commented 3 years ago

Closing this issue for now, since we had no feedback for few months. Feel free to re-open it if you're still experiencing the same problem.

ThatStevensGuy commented 3 years ago

Surprised no one else is experiencing this. I still require the meta_modified fix above to get TextControl to update.

If I am the edge case, then we can leave it closed, as I found that earlier work around fixes it for my needs.

wltrallen2 commented 1 year ago

Hey @ThatStevensGuy and @Mamaduka, I am experiencing the exact same issue. In my case, I am using the recommended useEntityProp as so:

    const [ meta, setMeta ] = useEntityProp('postType', postType, 'meta', postId)

And then, when I go to save a object with multiple vertical levels (i.e.

metadata_attribute: {
    element_01: {
        id_01: {
            param_1: value,
            param_2: value
       }
   }
}

I was able to retrieve edited values using select('core/editor').getEditedPostAttribute('meta'); however, when I viewed the postEdits using select('core/editor').getPostEdits()`, the edited metadata did not appear in the post edits (and would not save to the database).

When I added a timestamp to the top level object, the meta was added to the postEdits and saved to the database as expected.

cherrygot-personal commented 10 months ago

Ran into the same issue today and the date hack works absolutely fine. Although, it'd be great if there were some native API way of choosing what meta fields need to be deep compared and what not. Especially, when the WP team has already published an article https://make.wordpress.org/core/2019/10/03/wp-5-3-supports-object-and-array-meta-types-in-the-rest-api/, it'd be really nice if the gutenberg's support to save these fields is also brought into the picture.

Happy to help!

Mamaduka commented 10 months ago

@cherrygot-personal, can you provide a minimum code for reproduction?

cherrygot-personal commented 10 months ago

Sure, here's my use case.

I'm trying to save translation logs as an array of objects in the post meta. So I registered the following post meta, with the schema for the value it stores.

public static function register_custom_fields(): void {
  $args = array(
    'single' => true,
    'type' => 'array',
    'show_in_rest' => array(
      'schema' => array(
        'items' => array(
          'type' => 'object',
          'properties' => array(
            'region' => array(
              'type' => 'string',
            ),
            'url' => array(
              'type' => 'string',
              'format' => 'uri',
            ),
            'translator' => array(
              'type' => 'integer',
              'minimum' => 1,
            ),
            'date' => array(
              'type' => 'string',
              'format' => 'date-time',
            ),
          ),
        ),
      ),
    ),
  );
  \register_post_meta( 'post', Constants::FIELD_TRANSLATION_LOGS, $args );
}

On the frontend, I'm updating the logs by pushing the values to an array variable. And at the time of assignment, I'm using the following piece of code, inspired by the solution mentioned here.


  useEffect( () => {
    if ( ! hasStartedResolution || isResolving || ! areLogsUpdated ) {
      return;
    }

    dispatch( editorStore ).editPost( {
      meta: {
        /*
        It's a hack to make the complex meta types, like array and objects to be saved by
        the gutenberg editor. The logic is that the editor compares the entire post object it
        received from backend to the changes made on the editor. During that comparison, the code
        skips past the deeply nested objects for comparison and just compares top most items.

        As a hack, we can mention a top level meta with the latest date-time object to force
        gutenberg to compare it with the data it has. Since date time change frequently, it sort of
        revalidates the cache (I know, wrong analogy!)

        Read more here: https://github.com/WordPress/gutenberg/issues/25668
        */
        meta_modified: new Date().toISOString(),
        [ window.RegionGlot.FIELD_TRANSLATION_LOGS ]: logs
      },
    } );
  }, [ logs, isResolving, hasStartedResolution, areLogsUpdated ] );

This, so far, is the working state. However, previously, I was just assigning the meta like so.

// "logs" is an array. Let's say it has the following item.
    const logs = [
      {
        region: 'IT',
        date: '1980-10-10 10:00:00',
        translator: 24,
        url: 'https://google.com',
      }
    ];

    dispatch( editorStore ).editPost( {
      meta: {
        [ window.RegionGlot.FIELD_TRANSLATION_LOGS ]: logs
      },
    } );

But in this case, I never found any meta values being sent to the backend when I checked my networks tab.

When I assigned string-ified logs with JSON.stringify(), the request included the logs, but of course, it was a string. This solved the issue of request not including the meta data (like previously no meta data of any sort was included) but the backend was not ready to receive it as it was expecting an array, as we defined in the schema when registering the post meta.

So back to using array directly, without stringify, I tried to locate the problem and it's found here: https://github.com/WordPress/gutenberg/blob/b0848494cdbee4708ff44efa9fad88a17c24204d/packages/core-data/src/actions.js#L355C18-L355C25

The getRawEntityRecord and getEditedEntityRecord are returning the same meta data when fetched on line 336 and 337. Now I do want to mention that I'm using the gutenberg's version that it was on WP/6.1. It may have been addressed in the newer versions of it.

That date string changes the editedEntryRecord just enough that the line 355 recognizes the change and sends the updated post meta values.

Hope this helps!

goncalovf commented 6 months ago

I have the exact same issue. Thank you for the workaround

Geobusteni commented 1 day ago

Hei, it also happened to me and the solution above is working just fine. It might happen only on custom post types. I haven't tried this idea yet, as I am behind with the project due to many hours spent on thinking what is going on.

This thread shouldn't be closed as this is not solved yet.