Search code examples
jqueryreactjstwitter-bootstrapbootstrap-modalcarousel

How to get React.js and Bootstrap 4 modal carousel to always display the first image on open


I'm using React.js with a Bootstrap 4 modal/carousel and I want the first image to always be shown when the modal is opened/reopened. Right now the first image is shown the first time the modal is opened but when if the user scrolls to another image then closes and re-opens the modal, it shows the last image the user viewed and not always the first. I tried using jquery in my componentDidMount to force the carousel index but it's not working.

Here is an example scenario of my intended behavior...
1. Open Modal (First image is shown)
2. Carousel to 2nd item
3. Close Modal
4. Open Modal (First image is shown)

Here is a Demo

index.js

import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.scss';
import $ from 'jquery';

class App extends Component {
    constructor() {
        super();
    }

  componentDidMount() {
    // My attempts to always have carousel begin at index 0 on show event
    // $('.largeModal').on('show.bs.modal', function() {
        // console.log('show.bs.modal event');
        // $('.carousel').carousel(0);
    // });

    // $('.largeModal').on('show.bs.modal', function() {
        // console.log('show.bs.modal event');
        // $('#myCarousel').carousel(0);
    // });
  }

    render() {
        return (
            <div className='container'>
                <h3 className='text-center mb-4'>React with Bootstrap 4 Modal & Carousel</h3>

                <div class='row mb-4'>
                    <div class='col text-center'>
                        <button
                            type='button'
                            className='btn btn-primary '
                            data-toggle='modal'
                            data-target='#largeModal'
                        >
                            Open Modal
                        </button>
                    </div>
                </div>

                {/* Modal*/}
                <div
                    className='modal fade'
                    id='largeModal'
                    tabIndex='-1'
                    role='dialog'
                    aria-labelledby='basicModal'
                    aria-hidden='true'
                >
                    <div className='modal-dialog modal-lg'>
                        <div className='modal-content'>
                            <div className='modal-body'>
                                {/* Carousel */}
                                <div
                                    id='myCarousel'
                                    className='carousel slide'
                                    data-ride='carousel'
                                >
                                    <ol className='carousel-indicators'>
                                        <li
                                            data-target='#myCarousel'
                                            data-slide-to='0'
                                            className='active'
                                        ></li>
                                        <li
                                            data-target='#myCarousel'
                                            data-slide-to='1'
                                        ></li>
                                        <li
                                            data-target='#myCarousel'
                                            data-slide-to='2'
                                        ></li>
                                    </ol>
                                    <div className='carousel-inner'>
                                        <div className='carousel-item active'>
                                            <img
                                                className='img-size'
                                                src='https://images.unsplash.com/photo-1485470733090-0aae1788d5af?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1391&q=80'
                                                alt='First slide'
                                            />
                                        </div>
                                        <div className='carousel-item'>
                                            <img
                                                className='img-size'
                                                src='https://images.unsplash.com/photo-1491555103944-7c647fd857e6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80'
                                                alt='Second slide'
                                            />
                                        </div>
                                        <div className='carousel-item'>
                                            <img
                                                className='img-size'
                                                src='https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80'
                                                alt='Third slide'
                                            />
                                        </div>
                                    </div>
                                    <a
                                        className='carousel-control-prev'
                                        href='#myCarousel'
                                        role='button'
                                        data-slide='prev'
                                    >
                                        <span
                                            className='carousel-control-prev-icon'
                                            aria-hidden='true'
                                        >
                                            {' '}
                                        </span>
                                        <span className='sr-only'>Previous</span>
                                    </a>
                                    <a
                                        className='carousel-control-next'
                                        href='#myCarousel'
                                        role='button'
                                        data-slide='next'
                                    >
                                        <span
                                            className='carousel-control-next-icon'
                                            aria-hidden='true'
                                        ></span>
                                        <span className='sr-only'>Next</span>
                                    </a>
                                </div>
                            </div>
                            <div className='modal-footer'>
                                <button
                                    type='button'
                                    className='btn btn-default'
                                    data-dismiss='modal'
                                >
                                    Close
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

render(<App />, document.getElementById('root'));

style.scss

.img-size{
    height: 450px;
    width: 700px;
    background-size: cover;
    overflow: hidden;
}
.modal-content {
   width: 700px;
  border:none;
}
.modal-body {
   padding: 0;
}

.carousel-control-prev-icon {
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23009be1' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E");
    width: 30px;
    height: 48px;
}
.carousel-control-next-icon {
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23009be1' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E");
    width: 30px;
    height: 48px;
}

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>React Modal with Carousel</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</head>

<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
</body>

</html>

package.json

{
  "name": "react",
  "version": "0.0.0",
  "private": true,
  "dependencies": {
    "bootstrap": "^4.4.1",
    "jquery": "1.9.1 - 3",
    "node-sass": "^4.13.1",
    "popper.js": "^1.16.0",
    "react": "^16.9.0",
    "react-dom": "^16.9.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {
    "react-scripts": "latest"
  }
}

Solution

  • tl,dr;

    The imported jQuery in your App is not the same as the one in window object. Therefore is not extended by Bootstrap with .carousel() method, which you're using. You can get access to the one in window object by using

    const $ = window.$
    

    inside componentDidMount function. See it here.
    Or you could import popper.js and Bootstrap's js after jQuery and remove the <script> tags from index.html.


    Complete (initial) answer:

    First of all, there are a few problems with what you have right now:

    • your attempts were done with $('.largeModal') but I don't see any className="largeModal" in your code. You probably wanted to use #largeModal.
    • you have a few cases of class in your markup and React doesn't seem to like them
    • you're using Bootstrap v4.0.0. Regardless of major version, you should always go with the latest stable version of that major (current 4 would be v4.4.1 and current 3 would be v3.4.1).
    • you're loading jQuery twice. Once in index.html (from cdn) and once in your component (from packages). While loading jQuery more than once is possible (to allow different modules to use different versions of jQuery), there's a special technique for it and you're not using it (running jQuery in compatibility mode). I won't go into details as (at least for the current question) you shouldn't need it.

    Now, you have two options:

    a) load jQuery, popper.js and bootstrap as you did but then you have to also place your script inside index.html, after them (in a <script> tag, instead of in componentDidMount), and use something like:

    $(document).on('show.bs.modal', '#largeModal', function() {
      $('#myCarousel').carousel(0);
    })
    

    or...
    b) load jQuery, popper.js and bootstrap's js properly (with import, from their respective npm packages) inside the component.

    The main problem is that if you import only jQuery in your component (besides loading jquery twice, which is a problem in itself), it won't have the methods extended on it by bootstrap.js. And you seem to badly need one of them $().carousel().

    Now, I understand "b)" is not an ideal practical solution as you shouldn't have to import jQuery, popper and bootstrap in each of your components separately, the solution for that is to assign the external reference into your component by using const $ = window.$ inside componentDidMount, as outlined in tl,dr;.

    You can see it working (with imports) here.
    It also works from outside React (option "a)"). Just make sure you only load jQuery, popper.js and bootstraps js using one method, not both. I personally do not recommend option "a)" (read note).

    Cheers!


    Note: React and jQuery are two different web paradigms and using them together is going to make your life much harder (as a programmer). You'll probably be happier (and more proficient) by choosing one and sticking with it (most developers would advise you to drop jQuery and use React Bootstrap but the choice is entirely yours).