I can't figure out how to make popovers work, as per: https://getbootstrap.com/docs/4.0/components/popovers/.
The documentation talks about the popover support being a plugin and requiring the tooltip plugin as well, so I've modified my webpack.config.js
to add those two, now it looks like this:
...
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
Popper: ['popper.js', 'default'],
Popover: 'exports-loader?Popover!bootstrap/js/dist/popover',
Tooltip: "exports-loader?Tooltip!bootstrap/js/dist/tooltip",
}),
...
I haven't found any documentation about the Bootstrap plugin concept, so the above two lines for Popover
and Tooltip
came from a search, not sure if they're correct.
The documentation states:
Popovers are opt-in for performance reasons, so you must initialize them yourself.
But I don't understand how to do that.
The documentation shows the following code for initializing the popover:
$(function () {
$('.example-popover').popover({
container: 'body'
})
})
But I don't understand what that's supposed to be doing - there's no popover()
function on my element to invoke - how does this work?
Here's an example of my code that's trying to use a popover:
render(){
...
<span>
Summary info
<button
type="button" className="btn btn-sm"
data-toggle="popover" title="Popover title"
data-content="The popover Content"
>
Show popover
</button>
</span>
...
}
How do I make the Bootstrap V4 popover functionality work in the context of a React application? Specifically - how do I "initialize" the popover?
I'm using Typescript 2.3, React 16, Bootstrap 4 (no react-bootstrap style libraries).
Note that react-bootstrap supports only Bootstrap V3, and reactstrap is too unstable and I don't want to use it.
I had (still have, undoubtedly) a lot of missing contextual understanding when I was initially trying to get Bootstrap popovers working.
First thing is: the Bootstrap "Popover" plugin is really a JQuery plugin. I assume that's how all the Bootstrap plugins work, but I couldn't find any Bootstrap introductory documentation about this. So that explains the popover()
method and where it comes from.
Below, I've outlined what is needed to make the popovers work in the context of a React / Typescript / Webpack stack.
In the following, I'm assuming you've configured Webpack as per the Bootstrap doco.
You don't need the "Popover" and "Tooltip" lines from the original question, assuming you're webpack.config.js
is as per the Bootstrap doco and you have import 'bootstrap';
somewhere in your codebase.
You need to add the JQuery typings, if not already present, so that you can import $
and have the right types:
"devDependencies": {
"@types/jquery": "3.2.15",
...
}
"dependencies": {
"bootstrap": "4.0.0-beta",
"jquery": "3.2.1",
"popper.js": "1.11.0",
...
}
You have to extend the type definition of JQuery so that it knows about the Popover plugin, as per this blog article by Netanel Basal. In typings.d.ts
(or wherever makes sense in your project), add the following:
// support for JQuery popover plugin from Bootstrap 4
interface JQuery {
popover() : any;
}
Note that definition is the bare minimum you need to get popovers working from your code in a statically typed way. It needs to be extended to support passing parameters so that you can customise the popover behaviour. Or you could use the data
attributes to pass these parameters, as per the Live example in the doco.
In the React component itself, the JSX to declare the popover from the question seems to work fine.
To "initialize" the popover, as per the question, you need to import the JQuery identifier and then call the popover method:
...
const $ = require('jquery');
...
componentDidMount(): void{
$('[data-toggle="popover"]').popover();
}
...
The "import form" didn't work for me, so I had to require()
it.
That code searches the entire HTML page for elements with data-toggle="popover"
, and returns a JQuery object that has the popover()
method you can call (that's the whole JQuery plugin part).
Once popover()
has been called on the element with the popover attributes, popovers will be automatically displayed when the element is clicked (there's no need to manage popover-specific React state).
EDIT
It's not a good idea to search the entire HTML page for all popovers as shown above. In a complicated page with multiple popovers in multiple React components, each component would end up overwriting each other's popover()
options.
Here's my current solution for a re-usable React bootstrap Popover component.
Extend the Typescript typings to understand more popover options:
// support for JQuery popover plugin from Bootstrap 4
interface JQuery {
popover(options?: PopoverOptions) : any;
}
interface PopoverOptions {
container?: string | Element | boolean;
content?: string | Element | Function;
placement?: "auto" | "top" | "bottom" | "left" | "right" | Function;
title?: string | Element | Function;
...
}
Create Popover.tsx
like:
export interface PopoverProps {
popoverTitle: string | Element | Function;
popoverContent: string | Element | Function;
}
export class Popover
extends PureComponent<PopoverProps, object> {
selfRef: HTMLSpanElement;
componentDidMount(): void{
$(this.selfRef).popover({
container: this.selfRef,
placement: "auto",
title: this.props.popoverTitle,
content: this.props.popoverContent,
});
}
render(){
return <span
ref={(ref)=>{if(ref) this.selfRef = ref}}
data-toggle="popover"
>
{this.props.children}
</span>;
}
}
Then use the popover like:
<Popover
popoverTitle="The popover title"
popoverContent="The popover content"
>
<span>
Click this to show popover.
</span>
</Popover>
Beware issues related to Popover positioning with dynamic content: Bootstrap 4 - how does automatic Popover re-positioning work?
What the documentation is trying to say that simply having an element with data-toggle="popover" will not create an instance of a popover. You need to explicitly initialise it with a javascript call.
In a non-react environment, you could add it to the document ready function, for example like this
$(document).ready(function () {
$('.myPopoverItem').popover({
html: true
, trigger: 'focus'
, content: $.proxy(this.getContent, this)
});
}
In React though, you would need to add it to the constructor code of your object, such that it is called after render() (The elements have to be on the page).
I found this potential answer: React "after render" code?
Where it recommends the componentDidMount function in which you can call the popover initialisation.
So in your React object, you would have a function
componentDidMount() {
$('.myPopoverItem').popover({
html: true
, trigger: 'focus'
, content: $.proxy(this.getContent, this)
});
}