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
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)
});
}
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?