I have installed swiper slider (react version) and created a custom block. When I add the block in the backend it works swimmingly and I can swipe just fine. But when I save and view in the frontend nothing appears. When I check the error messages after refreshing in the backend I see this message:

Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app
import { registerBlockType } from "@wordpress/blocks";
import { useBlockProps } from "@wordpress/block-editor";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";

import "./editor.scss";
import "./style.scss";

import json from "./block.json";
import { __ } from "@wordpress/i18n";
const { name, ...settings } = json;

registerBlockType(name, {
    edit: (props) => {
        return (
            <div {...useBlockProps()}>
                    onSlideChange={() => console.log("slide change")}
                    onSwiper={(swiper) => console.log(swiper)}
                    <SwiperSlide>Slide 1</SwiperSlide>
                    <SwiperSlide>Slide 2</SwiperSlide>
                    <SwiperSlide>Slide 3</SwiperSlide>
                    <SwiperSlide>Slide 4</SwiperSlide>
    save: () => {
        return (
            <div {...useBlockProps.save()}>
                <Swiper spaceBetween={50} slidesPerView={3}>
                    <SwiperSlide>Slide 1</SwiperSlide>
                    <SwiperSlide>Slide 2</SwiperSlide>
                    <SwiperSlide>Slide 3</SwiperSlide>
                    <SwiperSlide>Slide 4</SwiperSlide>
  • your save function isn't meant to return an operational react component that executes on the frontend, it's meant to return the raw static HTML that gets saved to the database. It's up to you to transform that into a swiper slider on the frontend in JS much like you would a widget or shortcode. There's also a super important part of the error message that you've not shared to try and be helpful which is the stack trace that shows you where and when the calls were made.
    – Tom J Nowell
    Commented Dec 26, 2021 at 15:42
  • 1
    You might also find that Swiper does not "support" react in the way that you expect it to, it's just a wrapper around a non-React implementation that uses DOM hoisting, so it may not be as portable as you'd hoped. This becomes super obvious when you try to implement nested blocks via a slide block, those slides aren't' recognised as slides and get moved to after the slide area as an "after block" area. I've made PR's to try to and fix this in swiper which improved things, but a fix isn't possible without a re-architecting of how the swiper react code works
    – Tom J Nowell
    Commented Dec 26, 2021 at 15:44

1 Answer 1


You've misunderstood the purpose of the save component. The goal of the save component is not to return an interactive React component, it's to return something that can be converted into static HTML and saved in the database as a string in the post content.

Since only the HTML gets saved, it's your responsibility to load javascript on the frontend that finds this HTML and converts it into a Swiper slider.

So you should not attempt to do any of the following in the save component, directly or indirectly via a library:

  • event listeners
  • react state
  • callbacks/effects/HTTP requests/etc

A save component can even be ran when no saving is occurring, for example when validating a block by comparing the saved block to the save component.

So with this in mind, it makes no sense to use <Swiper> or <SwiperSlide> components here.

Instead, follow the Swiper documentation and generate actual HTML instead:

<!-- Slider main container -->
<div class="swiper">
  <!-- Additional required wrapper -->
  <div class="swiper-wrapper">
    <!-- Slides -->
    <div class="swiper-slide">Slide 1</div>
    <div class="swiper-slide">Slide 2</div>
    <div class="swiper-slide">Slide 3</div>
  <!-- If we need pagination -->
  <div class="swiper-pagination"></div>

  <!-- If we need navigation buttons -->
  <div class="swiper-button-prev"></div>
  <div class="swiper-button-next"></div>

  <!-- If we need scrollbar -->
  <div class="swiper-scrollbar"></div>


Adjust it for JSX and save it that way.

As an aside, the way the Swiper library detects the slides in the <Swiper> component is by inspecting the virtual dom and looking for direct descendants of type <SwiperSlide>. This means it cannot find the slides if you use Slide blocks. This means having user configurable slides is not possible in Gutenberg because of the higher order components used to provide block information. It's not possible to have nested blocks this way. Swiper will interpret child blocks as additional HTML to append after the slider, even if they're slides or <SwiperSlide> components.

  • Can I still use React Swiper slider in edit and then use the 'normal' html way you suggested for save ? Or were you saying I should not use React swiper slider at all? Commented Dec 26, 2021 at 17:54
  • the edit component generates the user interface in the editor that the user sees and interacts with. the save component generates static HTML to be saved to the database. I would also be wary of React Slider. Those are two separate unconnected statements.
    – Tom J Nowell
    Commented Dec 26, 2021 at 21:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.