How to show popup on page leave with React router? - reactjs

So I want to show user a popup when they try to leave the page. The popup will have to buttons - on to save content and leave and another to just leave without saving.
I'm using router 3.2.1
I tried to make use of routeLeaveHook functionallity, and it works to open the dialog.
But the problem I'm facing right now is I'm not sure how to handle two functions which should run on button clicks? Should I push to history in them to navigate to the clicked page? How can I get nextLocation there? Or should I use routeLeaveHook somehow? Or is there another way to do all of it?
Thank you for your help!
class MyDialog extends React.Component {
componentDidMount(): void {
const { route, routeLeaveHook} = this.props;
routeLeaveHook(route, this.routerWillLeave);
}
routerWillLeave = (nextLocation: string): any => {
if (dataToSave) {
...open dialog
}
return false;
}
render () {
<div open={isOpen}>
...some content
<button onClick={this.saveAndLeave}>
Save and leave
</button>
<button onClick={this.leave}>
Leave
</button>
</div>
}
}
UPDATE
Right now I think I should move my routerWillLeave logic to container of Dialog instead of putting it inside the dialog

https://codesandbox.io/s/myw173jyq8
hey check this code sandbox for navigation away using react router prompt with custom dialogue.

Related

React global click track handler

I am new to react and working on a legacy codebase. Am wondering if we can write a global button click handler for click tracking.
The jQuery equivalent of this would be something like,
utilities.js :
var subscribeForClickTracking = function() {
$( "button" ).click((event) => { console.log($(event.target).html()) })
$( "p" ).click((event) => { console.log($(event.target).html()) })
}
In all the html files, will add reference to utiliies.js and this snippet of code.
$(document).ready(function () {
subscribeForClickTracking();
});
I have surfed about this and reached similar so questions, like
Higher Order React Component for Click Tracking
and https://www.freecodecamp.org/news/how-to-track-user-interactions-in-your-react-app-b82f0bc4c7ff/
But these solutions involve modifying the button's implementation, which would lead to huge change. (For a html form with 50+ buttons).
Is there an alternate approach to achieve something similar to the above jQuery approach.
Thanks in advance.
No, you can not do that. The reason is that React prevents you from doing global things to avoid side effects.
I think the proper React way would be to create your own Button component.
First create a new component :
export default Button = (props) => <button ...props />
Then, you can import and use Button instead of button in any component.
Then in your Button component, you can override your onClick method like this :
<button
...props
onClick={() => {
// doWhatYouWantHere;
props.onClick()
/>
However, as React is JavaScript, you can still use vanilla JavaScript to attach an event to every button

How to run common code before all onclick handlers in a React app? (to work around Safari middle-button click bug)

How can I run code before all onclick handlers in a React app without having to add code to each handler? Specifically, I want to globally make sure that middle-button clicks are ignored by all React onclick handlers. The goal is to work around a 12-year-old WebKit bug where Safari emits a click event when the middle mouse button is pressed, instead of the auxclick event that's mandated by the W3C standard and that's emitted by Chrome and Firefox.
Because some users accidentally trigger middle button clicks while scrolling with the mousewheel, I'd like to ignore these accidental clicks globally. How?
The code I want to inject is very simple:
if (e.button !== 0) {
e.stopPropagation();
}
But I'm not sure where to inject it so that it will run before all event handlers in my app.
One potential complication is that I don't want to ignore middle clicks completely (because browsers have a default behavior that middle clicking on an <a> element will open the link in a new tab). Instead, I just want to prevent react from doing anything with those invalid click events.
To solve this I thought I'd have to do something tricky like monkey-patching React, but it turned out that a non-tricky solution was possible: just wrap the entire app in a top-level component that captures click events using the onClickCapture event instead of the normal click event. Here's a simple component I wrote for this purpose.
IgnoreSafariMiddleClicks.tsx
import React, { useCallback, MouseEventHandler, ReactNode } from 'react';
export default function IgnoreSafariMiddleClicks({ children }: { children: ReactNode }) {
const onClick = useCallback<MouseEventHandler>(e => {
if (e.button !== 0) {
// Prevent middle clicks from being handled by click handlers on Safari
// browsers, in order to work around this 12-year-old WebKit bug:
// https://bugs.webkit.org/show_bug.cgi?id=22382
e.stopPropagation();
}
}, []);
return <div onClickCapture={onClick}>{children}</div>;
}
If you're not using TypeScript, here's a plain JS version of the component:
IgnoreSafariMiddleClicks.js
import React from 'react';
export default function IgnoreSafariMiddleClicks({ children }) {
const onClick = useCallback(e => {
if (e.button !== 0) {
// Prevent middle clicks from being handled by click handlers on Safari
// browsers, in order to work around this 12-year-old WebKit bug:
// https://bugs.webkit.org/show_bug.cgi?id=22382
e.stopPropagation();
}
}, []);
return <div onClickCapture={onClick}>{children}</div>;
}
Usage
import React from 'react';
import IgnoreSafariMiddleClicks from './IgnoreSafariMiddleClicks';
export default function App() {
return (
<IgnoreSafariMiddleClicks>
<div>
<button onClick={() => console.log('Left clicked!')}>
click me!
</button>
</div>
</IgnoreSafariMiddleClicks>
);
}
One gotcha I discovered was that SyntheticEvent.nativeEvent.stopImmediatePropagation doesn't work in this scenario, because other React event handlers continue to be called afterwards. I had to use the stopPropagation method of SyntheticEvent.
It took a while for me to figure out this solution (especially the capture-phase trick and the stopPropagation vs. stopImmediatePropagation issue), and I didn't see this middle-button-swallowing solution anywhere else online, so posting it here to help the next person searching for a solution.
An alternative solution could be to add a polyfill that replaced Safari's bad click events with standards-compliant auxclick events, but Google didn't return anything promising and writing an event polyfill is beyond my limited knowledge of React's event handling, so I opted for the wrapper-component solution above.

How to refresh the facebook like url in reactjs (using facebook-sdk CDN)

I am using Facebook's like button as generated by facebook's like button configurator. However in order to get facebook-sdk to finish loading before the Like button, I had to use something called react-load-script and make a my own wrapper component for the like button html I got from the configurator.
my like button
class Like extends React.Component {
state = {
facebookLoaded: false
};
handleFacebookLoaded = () => this.setState({
facebookLoaded: true
});
FacebookSDK = () => <>
<div id="fb-root"></div>
<Script
async defer crossOrigin="anonymous"
url="https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v3.3&appId=391623981325884&autoLogAppEvents=1"
onLoad={this.handleFacebookLoaded}
/>
</>;
render() {
return <>
<this.FacebookSDK />
{this.state.facebookLoaded
? <div class="fb-like" data-href={this.props.url} data-width="" data-layout="button_count" data-action="like" data-size="large" data-show-faces="true" data-share="true" />
: null}
</>;
}
}
In my code all the script loading stuff actually happens in App.jsx, but I moved it into one class just to show a simple version.
This part seems to work fine, the issue lies when changing the url passed to data-href.
I checked the react dom in the browser and the data-href is actually being updated properly, however this does not affect the actual url that is being used by the like button, unless I do a full page refresh. I'm assuming this has to do with how the data-href is being used by facebook-sdk. (edit: after testing I'm not sure anymore)
I've found many questions about this on Stack Overflow, however none of them seem to be based off the CDN version of facebook buttons
From what I understand, the div containing the href needs to be placed out and back into the DOM in order for the facebook-sdk to detect a change, but I don't know how to do this in react without a full page refresh. Also I'm not certain this is even the right solution.
-- Update --
I just noticed something else that seems like useful information. If I navigate to the page with the like button, then it doesn't show up. It will only show up if the page refreshes. I tested it by moving the part that loads the script into the like component (like in the example shown above) and that didn't change the behavior at all.
-- more experimenting --
I wrote an event handler that takes all the facebook related jsx out of the dom and back in (by toggling a button) However when all the code goes back into the dom (both jsx and html), the UI for the button does not come back. I'm really now sure how this is possible as I'm literally reloading the script and everything facebook related so this should be equivalent to a page refresh no?
I fixed the issue thanks to misorude. The part I was missing was calling window.FB.XFBML.parse(). I didn't realize I could access FB the same way using the CDN. If anyone is looking for a react solution here is the working code:
class Like extends React.Component {
constructor(props) {
super(props);
this.state = {
url: props.url,
}
}
handleChangePage() {
let likeBtn = document.createElement('div');
likeBtn.className = "fb-like";
likeBtn.setAttribute("data-href", this.props.url);
likeBtn.setAttribute("data-width", "");
likeBtn.setAttribute("data-layout", "button_count");
likeBtn.setAttribute("data-action", "like");
likeBtn.setAttribute("data-size", "large");
likeBtn.setAttribute("data-show-faces", "true");
likeBtn.setAttribute("data-share", "true");
let likePanel = document.getElementById("like-panel");
likePanel.removeChild(likePanel.childNodes[0]);
likePanel.appendChild(likeBtn);
window.FB.XFBML.parse(likePanel)
this.setState({ url: this.props.url });
}
componentDidMount() {
this.handleChangePage();
}
render() {
if(this.props.url !== this.state.url)
this.handleChangePage();
return <div id="like-panel">
{this.props.facebookLoaded
? <div className="fb-like" data-href={this.props.url} data-width="" data-layout="button_count" data-action="like" data-size="large" data-show-faces="true" data-share="true" />
: null}
</div>;
}
}
I moved the CDN out of this component so that it only loads the sdk once for the whole app.

Mimic React custom component

I have a custom Reactjs component to display Pagination with next/previous buttons at the bottom of a grid. Now, the business needs to display the same component on top of the grid as well. How to display the previous /next button events based on the input provided in prev/next buttons at the bottom of the grid?
I tried using javascript innerHTML to mimic the behaviour. It works only with the display. It does not attach the event listener of the buttons. I tried even with
document.querySelector.addEventListener('click', ()=>{console.log('test')})
It does not work. Is there a better way to do with react.
I am going to just add some more content to Shmili Breuer answer.
If i understood you correctly you have 2 navigations, one at the top one at the bottom. The way you connect them would be through a state of you component, or a parent component if you are using functional component to render pagination stuff. So if you change the state it will reflect on both of your navigations. Also you can use only one function here, by passing a parameter, im gonna copy a code from before mentioned answer.
// first create a function
nextFunction = (condition) => {
if(condition){
this.setState(prevState=>({
page: prevState.page-1
}))
} else {
this.setState(prevState=>({
page: prevState.page+1
}))
}
}
// then use it in your button
<button onClick={() => this.nextFunction(some condition)}>Next</button>
Just put that component on top and bottom
<Grid>
<Pagination />
{...someOtherComponents}
<Pagination />
</Grid>
it's ok in react. Optimization that you want to do is overhead.
In react you would add an onClick attribute to an element you want to handle a click on.
Something like this
// first create a function
nextFunction = () => {
do next functionality....
}
// then use it in your button
<button onClick={() => this.nextFunction()}>Next</button>
This way you can have both top and bottom pagination call the same function.
Hope this helps

Link in Infobox using react-google-maps causes page reload

I'm not able to put a react-router-dom Link inside of a react-google-maps InfoBox without causing a full page reload.
Here's the code for my InfoBox:
import InfoBox from 'react-google-maps/lib/components/addons/InfoBox'
import { Link } from "react-router-dom";
class MyInfoBox extends Component {
...
const options = {
enableEventPropagation:false,
position: new google.maps.LatLng(loc.latitude, loc.longitude)
};
render(){
return (
<InfoBox options={options}>
<Link to={`/location/${loc.id}`}>Go To Location</Link>
</InfoBox>
);
}
}
All Links in my app work correctly, except for this one.
When the "Go To Location" link is clicked, a full-page reload is caused. I've tried to diagnose by following this issue: https://github.com/tomchentw/react-google-maps/issues/258, but I really don't know enough about the react router v4 to find out if the context contains the router object. Changing the value of enableEventPropagation does not change the behavior.
If anyone can help me understand why the Link is causing a page reload, I would really appreciate it.
Thank you in advance!
Try removing onClick prop in <GoogleMap> component and add enableEventPropagation: true to <InfoBox> options.
For me the issue was that <GoogleMap> onClick event was used to remove <InfoBox> when user clicks outside it. But turns out it is called also inside <InfoBox> so basically it was removing <InfoBox> with the <Link> inside it, before handling <Link> click.

Resources