Open toughbax opened 4 years ago
Hi @toughbax
Thanks for the topic.
Manipulating a React constructed DOM (JSX) is a delicate operation, which may not always be possible - especially when using external JS libraries.
Can you post up the full code for your block so I may test it out on my local site?
Hi @elliotcondon,
Thanks for the quick response.
Here is a modified version of the basic carousel block (to include jsx and the innerblocks). I adjusted the javascript a bit to 'unslick' a carousel if it's already been slicked. This fixes the error when adding a slide to the block, however removing a block still has the error. It makes me think there may be a possible fix with some extra JS to adjust removing blocks as well. I don't know enough about the behind the scenes of the jsx stuff and think I hit a dead end with my ability to debug this further. Apologies if I missed something obvious.
Block registeration
<?php
// Register a slider block.
add_action('acf/init', 'my_register_blocks');
function my_register_blocks() {
// check function exists.
if( function_exists('acf_register_block_type') ) {
// register a testimonial block.
acf_register_block_type(array(
'name' => 'slider',
'title' => __('Slider'),
'description' => __('A custom slider block.'),
'render_template' => 'template-parts/blocks/slider/slider.php',
'category' => 'formatting',
'icon' => 'images-alt2',
'align' => 'full',
'supports' => array(
'align' => true,
'mode' => false,
'jsx' => true
),
'enqueue_assets' => function(){
wp_enqueue_style( 'slick', 'http://cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css', array(), '1.8.1' );
wp_enqueue_style( 'slick-theme', 'http://cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick-theme.css', array(), '1.8.1' );
wp_enqueue_script( 'slick', 'http://cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js', array('jquery'), '1.8.1', true );
wp_enqueue_style( 'block-slider', get_template_directory_uri() . '/template-parts/blocks/slider/slider.css', array(), '1.0.0' );
wp_enqueue_script( 'block-slider', get_template_directory_uri() . '/template-parts/blocks/slider/slider.js', array(), '1.0.0', true );
},
));
}
}
Block Template: (/template-parts/blocks/slider/slider.php)
<?php
/**
* Slider Block Template.
*
* @param array $block The block settings and attributes.
* @param string $content The block inner HTML (empty).
* @param bool $is_preview True during AJAX preview.
* @param (int|string) $post_id The post ID this block is saved to.
*/
// Create id attribute allowing for custom "anchor" value.
$id = 'slider-' . $block['id'];
if( !empty($block['anchor']) ) {
$id = $block['anchor'];
}
// Create class attribute allowing for custom "className" and "align" values.
$className = 'slider-block';
if( !empty($block['className']) ) {
$className .= ' ' . $block['className'];
}
if( !empty($block['align']) ) {
$className .= ' align' . $block['align'];
}
if( $is_preview ) {
$className .= ' is-admin';
}
?>
<div id="<?php echo esc_attr($id); ?>" class="<?php echo esc_attr($className); ?>">
<div class="innerblock-area"> <InnerBlocks /> </div>
<div class="slider">
<?php if( have_rows('slides') ): ?>
<div class="slides">
<?php while( have_rows('slides') ): the_row(); ?>
<?php $image = get_sub_field('image'); ?>
<div>
<?php echo wp_get_attachment_image( $image['id'], 'full' ); ?>
</div>
<?php endwhile; ?>
</div>
<?php else: ?>
<p>Please add some slides.</p>
<?php endif; ?>
</div>
</div>
Block CSS (/template-parts/blocks/slider/slider.css)
.slider-block .innerblock-area {
padding: 10px;
border: solid 1px black;
}
.slider {
padding: 0 0 5px;
}
.slider .slides img {
height: 80vh;
width: auto;
padding: 2vh;
}
.slider .is-admin:before {
display: block;
content: '';
top: 0;
left: 0;
right: 0;
bottom: 40px;
position: absolute;
z-index: 1;
}
Block JS (/template-parts/blocks/slider/slider.js)
(function ($) {
/**
* initializeBlock
*
* Adds custom JavaScript to the block HTML.
*
* @date 15/4/19
* @since 1.0.0
*
* @param object $block The block jQuery element.
* @param object attributes The block attributes (only available when editing).
* @return void
*/
var initializeBlock = function ($block) {
var sliders = $block.find('.slides');
// If I run a check to unslick element first and re-slick it works for adding slides,
// But the error still happens when removing slides.
if (window.acf) {
if (sliders.hasClass('slick-initialized')) {
sliders.slick('unslick');
}
}
sliders.slick({
dots: true,
infinite: true,
speed: 300,
slidesToShow: 1,
centerMode: true,
variableWidth: true,
adaptiveHeight: true,
focusOnSelect: true,
});
};
// Initialize each block on page load (front end).
$(document).ready(function () {
$('.slider').each(function () {
initializeBlock($(this));
});
});
// Initialize dynamic block preview (editor).
if (window.acf) {
window.acf.addAction(
'render_block_preview/type=slider',
initializeBlock
);
}
})(jQuery);
ACF Fields (/acf-json)
{
"key": "group_5f80fb0c42f99",
"title": "Block : Slider",
"fields": [
{
"key": "field_5f80fb12325a1",
"label": "Slides",
"name": "slides",
"type": "repeater",
"instructions": "",
"required": 0,
"conditional_logic": 0,
"wrapper": {
"width": "",
"class": "",
"id": ""
},
"collapsed": "",
"min": 0,
"max": 0,
"layout": "table",
"button_label": "",
"sub_fields": [
{
"key": "field_5f80fb1a325a2",
"label": "Image",
"name": "image",
"type": "image",
"instructions": "",
"required": 0,
"conditional_logic": 0,
"wrapper": {
"width": "",
"class": "",
"id": ""
},
"return_format": "array",
"preview_size": "medium",
"library": "all",
"min_width": "",
"min_height": "",
"min_size": "",
"max_width": "",
"max_height": "",
"max_size": "",
"mime_types": ""
}
]
}
],
"location": [
[
{
"param": "block",
"operator": "==",
"value": "acf\/slider"
}
]
],
"menu_order": 0,
"position": "normal",
"style": "default",
"label_placement": "top",
"instruction_placement": "label",
"hide_on_screen": "",
"active": true,
"description": "",
"modified": 1602288426
}
Hi @toughbax. Thanks for the reply and snippets - this made light work to replicate the issue.
The problem you are facing is a perfect example of how JSX does not work with external rendering logic. In your situation, the slick slider library is modifying the DOM structure within .slides
which then causes React to fail when attempting to redraw the element.
React expects the Component render function to be the only logic that controls HTML. There are a few ways to work around this using jQuery, but this would require a lot of tricky code which may also end up not being possible in the long run.
For now, I'll have to say that it isn't possible to integrate slick slider into a JSX enabled block type. That said, perhaps you could use two blocks to accomplish this - one to act as the JSX wrapper (perhaps this could even be a cor group block?) and the second to perform the custom slider (where JSX = false).
Looking to the future, I do have an idea on how this may be possible. You mentioned the use of "unslick" to remove the Slick slider logic. In theory, if you were to call this "before" the React component render, then the DOM may be JSX readable (I say maybe because the DOM elements may still have been changed by slick slider). Leave this with me for now, and I'll hope to look into this one day 👍.
Thanks for the response @elliotcondon.
That makes complete sense with the JS manipulating the DOM.
I think your workaround ideas should suffice for most use cases. (In my particular case I wasn't even using an Innerblock, I mistakenly had 'jsx' enabled still though, so disabling it entirely was a good solution for me this time).
Your idea of being able to run some custom JS "before" the React component render also seems like a good idea. It would in theory allow clean up of any changes made via JS to prevent this error. Maybe another hook besides 'render_block_preview' that we could hook into (I say that not knowing what would actually be involved in that). It does seem like a bit of an edge case though, and with your multiple block workaround idea it doesn't seem like something that needs a high priority for a fix. I'm happy with the 'hope to look into this one day', I think that is a good place to leave it.
Thanks for all the great work with ACF, I appreciate it!
I've run into an issue where enabling
'jsx' => true
support breaks a block preview in the admin if I add or remove a row from a repeater field that is connected to JS (Specifically in this case the Slick Carousel JS).I am able to replicate using the block example from the 'building a custom slider in 30 minutes' blog post. (https://www.advancedcustomfields.com/blog/building-a-custom-slider-block-in-30-minutes-with-acf/)
It works as intended without
'jsx' => true
support, but as soon as I add that into theacf_register_block_type()
function and try and add or remove slides it breaks the entire block preview and the page needs to be refreshed to see the block again.The console provides the following error message
DOMException: Node.removeChild: The node to be removed is not a child of this node