From 2bb1ce5a73eb01256775a1a5de0118abfd8fec80 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Tue, 29 Jan 2019 13:35:31 -0800 Subject: [PATCH 01/12] Update edit examples with ES5 code --- .../developers/block-api/block-edit-save.md | 108 ++++++++++++++++-- 1 file changed, 97 insertions(+), 11 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index 1bfaf08f10a22e..098fad6973a025 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -6,25 +6,52 @@ When registering a block, the `edit` and `save` functions provide the interface The `edit` function describes the structure of your block in the context of the editor. This represents what the editor will render when the block is used. +{% codetabs %} +{% ES5 %} ```js -// Defining the edit interface +// A static div +edit() { + return wp.element.createElement( + 'div', + null, + 'Your block.' + ); +} +``` +{% ESNext %} +```jsx edit() { - return
; + return
Your block.
; } ``` +{% end %} The function receives the following properties through an object argument: ### attributes -This property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function: +This property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. See [attributes documentation](/docs/designers-developers/developers/block-api/block-attributes/) for how to specify attribute sources. +In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function: + +{% codetabs %} +{% ES5 %} +```js +edit( props ) { + return wp.element.createElement( + 'div', + null, + props.attributes.content + ); +} +``` +{% ESNext %} ```js -// Defining the edit interface edit( { attributes } ) { return
{ attributes.content }
; } ``` +{% end %} The value of `attributes.content` will be displayed inside the `div` when inserting the block in the editor. @@ -32,23 +59,53 @@ The value of `attributes.content` will be displayed inside the `div` when insert This property returns the class name for the wrapper element. This is automatically added in the `save` method, but not on `edit`, as the root element may not correspond to what is _visually_ the main element of the block. You can request it to add it to the correct element in your function. +{% codetabs %} +{% ES5 %} +```js +edit( props ) { + return wp.element.createElement( + 'div', + { className: props.className }, + props.attributes.content + ); +} +``` +{% ESNext %} ```js -// Defining the edit interface edit( { attributes, className } ) { return
{ attributes.content }
; } ``` +{% end %} ### isSelected The isSelected property is an object that communicates whether the block is currently selected. +{% codetabs %} +{% ES5 %} ```js -// Defining the edit interface +edit( props ) { + return wp.element.createElement( + 'div', + { className: props.className }, + [ + 'Your block.', + props.isSelected ? wp.element.createElement( + 'span', + null, + 'Shows only when the block is selected.' + ) + ] + ); +} +``` +{% ESNext %} +```jsx edit( { attributes, className, isSelected } ) { return (
- { attributes.content } + Your block. { isSelected && Shows only when the block is selected. } @@ -56,13 +113,38 @@ edit( { attributes, className, isSelected } ) { ); } ``` +{% end %} ### setAttributes This function allows the block to update individual attributes based on user interactions. +{% codetabs %} +{% ES5 %} ```js -// Defining the edit interface +edit: ( props ) => { + // Simplify access to attributes + let content = props.attributes.content; + let mySetting = props.attributes.mySetting; + + // Toggle a setting when the user clicks the button + let toggleSetting = () => props.setAttributes( { mySetting: ! mySetting } ); + return wp.element.createElement( + 'div', + { className: props.className }, + [ + count, + props.isSelected ? wp.element.createElement( + 'button', + { onClick: toggleSetting }, + 'Toggle setting' + ) : null + ] + ); +}, +``` +{% ESNext %} +```jsx edit( { attributes, setAttributes, className, isSelected } ) { // Simplify access to attributes const { content, mySetting } = attributes; @@ -79,6 +161,7 @@ edit( { attributes, setAttributes, className, isSelected } ) { ); } ``` +{% end %} When using attributes that are objects or arrays it's a good idea to copy or clone the attribute prior to updating it: @@ -93,7 +176,6 @@ const addListItem = ( newListItem ) => { list.push( newListItem ); setAttributes( { list } ); }; - ``` Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, Gutenberg follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes. @@ -106,13 +188,17 @@ The `save` function defines the way in which the different attributes should be {% ES5 %} ```js save() { - return wp.element.createElement( 'hr' ); + return wp.element.createElement( + 'div', + null, + 'Your block.' + ); } ``` {% ESNext %} ```jsx save() { - return
; + return
Your block.
; } ``` {% end %} From c01f0d9c7831a3444358ba986d66761dd5978976 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Tue, 29 Jan 2019 13:49:30 -0800 Subject: [PATCH 02/12] Add extra explanation and links to attributes and block tutorial --- .../developers/block-api/block-edit-save.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index 098fad6973a025..dba28a7f971487 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -30,7 +30,7 @@ The function receives the following properties through an object argument: ### attributes -This property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. See [attributes documentation](/docs/designers-developers/developers/block-api/block-attributes/) for how to specify attribute sources. +This property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. See [attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for how to specify attribute sources. In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function: @@ -232,6 +232,12 @@ save( { attributes } ) { ``` {% end %} + +When saving your block, you want save the attributes in the same format specified by the attribute source definition. If no attribute source is specified, the attribute will be saved to the block's comment delimiter. See the [Block Attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for more details. + +For a full example, see the [Introducing Attributes and Editable Fields](/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md) section of the Block Tutorial. + + ## Validation When the editor loads, all blocks within post content are validated to determine their accuracy in order to protect against content loss. This is closely related to the saving implementation of a block, as a user may unintentionally remove or modify their content if the editor is unable to restore a block correctly. During editor initialization, the saved markup for each block is regenerated using the attributes that were parsed from the post's content. If the newly-generated markup does not match what was already stored in post content, the block is marked as invalid. This is because we assume that unless the user makes edits, the markup should remain identical to the saved content. From 19690f9e6b67a20a2f3295fd19ad9747a368ce3c Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Tue, 29 Jan 2019 15:06:49 -0800 Subject: [PATCH 03/12] Add two examples for attributes, edit, save --- .../developers/block-api/block-edit-save.md | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index dba28a7f971487..fa408f580c4a58 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -235,7 +235,125 @@ save( { attributes } ) { When saving your block, you want save the attributes in the same format specified by the attribute source definition. If no attribute source is specified, the attribute will be saved to the block's comment delimiter. See the [Block Attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for more details. -For a full example, see the [Introducing Attributes and Editable Fields](/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md) section of the Block Tutorial. +## Examples + +Here are a couple examples of using attributes, edit, and save all together. For a full working example, see the [Introducing Attributes and Editable Fields](/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md) section of the Block Tutorial. + +### Saving Attributes to Child Elements + +{% codetabs %} +{% ES5 %} +```js +attributes: { + content: { + type: 'string', + source: 'html', + selector: 'p' + } +}, + +edit: ( props ) => { + updateFieldValue = function( val ) { + props.setAttributes( { content: val } ); + } + return wp.element.createElement( + wp.components.TextControl, + { + label: 'My Text Field', + value: props.attributes.content, + onChange: updateFieldValue, + + } + ); +}, + +save: ( props ) => { + return el( 'p', {}, props.attributes.content ); +}, +``` +{% ESNext %} +```jsx +attributes: { + content: { + type: 'string', + source: 'html', + selector: 'p' + } +}, + +edit: ( { attributes, setAttributes } ) => { + updateFieldValue = ( val ) { + setAttributes( { content: val } ); + } + return ; +}, + +save: ( { attributes } ) => { + return

{ attributes.content }

; +}, +``` +{% end %} + +### Saving Attributes via Serialization + +Ideally, the attributes saved should be included in the markup. However, there are times when this is not practical, so if no attribute source is specified the attribute is serialized and saved to the block's comment delimiter. + +This example could be for a dynamic block, such as the [Latest Posts block](https://github.com/WordPress/gutenberg/blob/master/packages/block-library/src/latest-posts/index.js), which renders the markup server-side. The save function is still required, however in this case it simply returns null since the block is not saving content from the editor. + +{% codetabs %} +{% ES5 %} +```js +attributes: { + postsToShow: { + type: 'number', + } +}, + +edit: ( props ) => { + return wp.element.createElement( + wp.components.TextControl, + { + label: 'Number Posts to Show', + value: props.attributes.postsToShow, + onChange: ( val ) => { + props.setAttributes( { postsToShow: parseInt( val ) } ); + }, + } + ); +}, + +save: () => { + return null; +} +``` +{% ESNext %} +```jsx +attributes: { + postsToShow: { + type: 'number', + } +}, + +edit: ( { attributes, setAttributes } ) => { + return { + setAttributes( { postsToShow: parseInt( val ) } ); + }}, + } + ); +}, + +save: () => { + return null; +} +``` +{% end %} ## Validation From fca94dc5dbcc6b8a57622a5d31fe32e2c57c700c Mon Sep 17 00:00:00 2001 From: Chris Van Patten Date: Thu, 31 Jan 2019 13:06:10 -0800 Subject: [PATCH 04/12] Update docs/designers-developers/developers/block-api/block-edit-save.md Co-Authored-By: mkaz --- .../developers/block-api/block-edit-save.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index fa408f580c4a58..be41cc08ab8e11 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -233,7 +233,7 @@ save( { attributes } ) { {% end %} -When saving your block, you want save the attributes in the same format specified by the attribute source definition. If no attribute source is specified, the attribute will be saved to the block's comment delimiter. See the [Block Attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for more details. +When saving your block, you want to save the attributes in the same format specified by the attribute source definition. If no attribute source is specified, the attribute will be saved to the block's comment delimiter. See the [Block Attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for more details. ## Examples From b6fdcd4b2e5cbed5cb61353c16986135358e8b3e Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Thu, 31 Jan 2019 13:18:07 -0800 Subject: [PATCH 05/12] Remove spaces. --- .../developers/block-api/block-edit-save.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index be41cc08ab8e11..0db9b9e20e4f69 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -21,7 +21,7 @@ edit() { {% ESNext %} ```jsx edit() { - return
Your block.
; + return
Your block.
; } ``` {% end %} From ad6d74dbbddce490f1c69a00b5e85d8c0b6615eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Fri, 1 Feb 2019 06:20:50 -0800 Subject: [PATCH 06/12] Update docs/designers-developers/developers/block-api/block-edit-save.md Co-Authored-By: mkaz --- .../developers/block-api/block-edit-save.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index 0db9b9e20e4f69..4c222d1bb03495 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -282,7 +282,7 @@ attributes: { }, edit: ( { attributes, setAttributes } ) => { - updateFieldValue = ( val ) { + const updateFieldValue = ( val ) => { setAttributes( { content: val } ); } return Date: Fri, 1 Feb 2019 06:21:01 -0800 Subject: [PATCH 07/12] Update docs/designers-developers/developers/block-api/block-edit-save.md Co-Authored-By: mkaz --- .../developers/block-api/block-edit-save.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index 4c222d1bb03495..881037f1a3f286 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -253,7 +253,7 @@ attributes: { }, edit: ( props ) => { - updateFieldValue = function( val ) { + var updateFieldValue = function( val ) { props.setAttributes( { content: val } ); } return wp.element.createElement( From a8da940badb66fa957d75f8f370a6314ca3cdd55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Fri, 1 Feb 2019 06:21:13 -0800 Subject: [PATCH 08/12] Update docs/designers-developers/developers/block-api/block-edit-save.md Co-Authored-By: mkaz --- .../developers/block-api/block-edit-save.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index 881037f1a3f286..b33ce1e73b4138 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -319,7 +319,7 @@ edit: ( props ) => { { label: 'Number Posts to Show', value: props.attributes.postsToShow, - onChange: ( val ) => { + onChange: function( val ) { props.setAttributes( { postsToShow: parseInt( val ) } ); }, } From 8927daa4706f42ff06668d891cafe035cef01f8d Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Fri, 1 Feb 2019 06:46:51 -0800 Subject: [PATCH 09/12] Make function defns consistent across ES5/ESNext --- .../developers/block-api/block-edit-save.md | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index b33ce1e73b4138..f22c0213852aa8 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -10,7 +10,7 @@ The `edit` function describes the structure of your block in the context of the {% ES5 %} ```js // A static div -edit() { +edit: function() { return wp.element.createElement( 'div', null, @@ -20,7 +20,7 @@ edit() { ``` {% ESNext %} ```jsx -edit() { +edit: () => { return
Your block.
; } ``` @@ -37,7 +37,7 @@ In this case, assuming we had defined an attribute of `content` during block reg {% codetabs %} {% ES5 %} ```js -edit( props ) { +edit: function( props ) { return wp.element.createElement( 'div', null, @@ -47,7 +47,7 @@ edit( props ) { ``` {% ESNext %} ```js -edit( { attributes } ) { +edit: ( { attributes } ) => { return
{ attributes.content }
; } ``` @@ -62,7 +62,7 @@ This property returns the class name for the wrapper element. This is automatica {% codetabs %} {% ES5 %} ```js -edit( props ) { +edit: function( props ) { return wp.element.createElement( 'div', { className: props.className }, @@ -72,7 +72,7 @@ edit( props ) { ``` {% ESNext %} ```js -edit( { attributes, className } ) { +edit: ( { attributes, className } ) => { return
{ attributes.content }
; } ``` @@ -85,7 +85,7 @@ The isSelected property is an object that communicates whether the block is curr {% codetabs %} {% ES5 %} ```js -edit( props ) { +edit: function( props ) { return wp.element.createElement( 'div', { className: props.className }, @@ -102,7 +102,7 @@ edit( props ) { ``` {% ESNext %} ```jsx -edit( { attributes, className, isSelected } ) { +edit: ( { attributes, className, isSelected } ) => { return (
Your block. @@ -122,7 +122,7 @@ This function allows the block to update individual attributes based on user int {% codetabs %} {% ES5 %} ```js -edit: ( props ) => { +edit: function( props ) { // Simplify access to attributes let content = props.attributes.content; let mySetting = props.attributes.mySetting; @@ -145,7 +145,7 @@ edit: ( props ) => { ``` {% ESNext %} ```jsx -edit( { attributes, setAttributes, className, isSelected } ) { +edit: ( { attributes, setAttributes, className, isSelected } ) => { // Simplify access to attributes const { content, mySetting } = attributes; @@ -187,7 +187,7 @@ The `save` function defines the way in which the different attributes should be {% codetabs %} {% ES5 %} ```js -save() { +save: function() { return wp.element.createElement( 'div', null, @@ -197,7 +197,7 @@ save() { ``` {% ESNext %} ```jsx -save() { +save: () => { return
Your block.
; } ``` @@ -216,7 +216,7 @@ As with `edit`, the `save` function also receives an object argument including a {% codetabs %} {% ES5 %} ```js -save( props ) { +save: function( props ) { return wp.element.createElement( 'div', null, @@ -226,7 +226,7 @@ save( props ) { ``` {% ESNext %} ```jsx -save( { attributes } ) { +save: ( { attributes } ) => { return
{ attributes.content }
; } ``` @@ -252,7 +252,7 @@ attributes: { } }, -edit: ( props ) => { +edit: function( props ) { var updateFieldValue = function( val ) { props.setAttributes( { content: val } ); } @@ -267,7 +267,7 @@ edit: ( props ) => { ); }, -save: ( props ) => { +save: function( props ) { return el( 'p', {}, props.attributes.content ); }, ``` @@ -313,7 +313,7 @@ attributes: { } }, -edit: ( props ) => { +edit: function( props ) { return wp.element.createElement( wp.components.TextControl, { @@ -326,7 +326,7 @@ edit: ( props ) => { ); }, -save: () => { +save: function() { return null; } ``` From 3dc7a49d452717407174032cdefab97a30761519 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Fri, 1 Feb 2019 09:40:19 -0800 Subject: [PATCH 10/12] Add clone example for ES5 --- .../developers/block-api/block-edit-save.md | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index f22c0213852aa8..c29908be824555 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -165,18 +165,41 @@ edit: ( { attributes, setAttributes, className, isSelected } ) => { When using attributes that are objects or arrays it's a good idea to copy or clone the attribute prior to updating it: +{% codetabs %} +{% ES5 %} ```js -// Good - here a new array is created from the old list attribute and a new list item: +// Good - cloning the old list to a new array and then adding item + +var listObj = Object.assign( {}, attributes.list ); + +// convert listObj back to array +var newList = Object.keys( listObj ).map( i => listObj[i] ); + +var addListItem = function( newListItem ) { + setAttributes( { list: newList.concat( [ newListItem ] ) } ); +}; + +// Bad - the list from the existing attribute is modified directly to add the new list item: +var list = attributes.list; +var addListItem = function( newListItem ) { + list.push( newListItem ); + setAttributes( { list: list } ); +}; +``` +{% ESNext %} +```js +// Good - a new array is created from the old list attribute and a new list item: const { list } = attributes; const addListItem = ( newListItem ) => setAttributes( { list: [ ...list, newListItem ] } ); -// Bad - here the list from the existing attribute is modified directly to add the new list item: +// Bad - the list from the existing attribute is modified directly to add the new list item: const { list } = attributes; const addListItem = ( newListItem ) => { list.push( newListItem ); setAttributes( { list } ); }; ``` +{% end %} Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, Gutenberg follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes. From 1eb025ddc3648dc734a06150ecfdc49c21dd9e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Mon, 4 Feb 2019 06:28:00 -0800 Subject: [PATCH 11/12] Update docs/designers-developers/developers/block-api/block-edit-save.md Co-Authored-By: mkaz --- .../developers/block-api/block-edit-save.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index c29908be824555..e39b22053ae57f 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -133,7 +133,7 @@ edit: function( props ) { 'div', { className: props.className }, [ - count, + content, props.isSelected ? wp.element.createElement( 'button', { onClick: toggleSetting }, From 6fc0a3532174d199f1e859cddffcf0c9fb5ce0b4 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Mon, 4 Feb 2019 08:52:18 -0800 Subject: [PATCH 12/12] Simplify ES5 example, props @nosolosw --- .../developers/block-api/block-edit-save.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index e39b22053ae57f..79a3206326031d 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -168,12 +168,8 @@ When using attributes that are objects or arrays it's a good idea to copy or clo {% codetabs %} {% ES5 %} ```js -// Good - cloning the old list to a new array and then adding item - -var listObj = Object.assign( {}, attributes.list ); - -// convert listObj back to array -var newList = Object.keys( listObj ).map( i => listObj[i] ); +// Good - cloning the old list +var newList = attributes.list.slice(); var addListItem = function( newListItem ) { setAttributes( { list: newList.concat( [ newListItem ] ) } );