NextJs script component should hide on a specific page - reactjs

In my React/NextJS app I added a new component to show a FreshDesk widget.
This component should only show on some specific pages after a login but when I logout from the app the widget is still visible until I refresh the page
The component
import Script from 'next/script';
const FreshDesk = () => {
const { freshDesk } = useConfig();
return (
<>
{console.log(freshDesk)}
<Script
src={`https://widget.freshworks.com/widgets/${freshDesk}.js`}
strategy="lazyOnload"
/>
<Script id="my-script" strategy="afterInteractive">
{` window.fwSettings={
'widget_id':${freshDesk},
};
!function(){if("function"!=typeof window.FreshworksWidget){var n=function(){n.q.push(arguments)};n.q=[],window.FreshworksWidget=n}}()
`}
</Script>
</>
);
};
export default FreshDesk;
It is just called <FreshDesk /> when it needs it.
The login page doesn't have the widget call but soon as I log out the widget is still there until I refresh and don't know what to do about it.

It looks like you can use FreshworksWidget("destroy") to destroy the widget.
source
I would propose you add the following useEffect (disclaimer: I don't know the library so this might not work perfectly)
useEffect(()=>{
// create the widget if the script is loaded already
if(window.FreshworksWidget){
window.FreshworksWidget("boot");
}
// when unmounting the component, destroy the FreshworksWidget
return ()=>{
if(window.FreshworksWidget){
window.FreshworksWidget("destroy");
}
}
}, []);

Decided to answer my own question by showing the solution I found working for me.
What I did was to add in the login component this code
useEffect(() => {
if (document.querySelector('#launcher-frame')) {
document.querySelector('#launcher-frame').style.display = 'none';
}
}
and in the component, I was using the widget
useEffect(() => {
if (document.querySelector('#launcher-frame')) {
document.querySelector('#launcher-frame').style.display = 'block';
}
}, []);
In this way, I just hide it from my login page but showing everywhere else

Related

Prompt user when url changes using react-router-dom

Assuming I am within an edit form page that has the url route of /person/edit/123
Within my react app, I also have a website logo at the top left of my app that when clicked, returns the user to the url route /home
Using react-router-dom v6 or some other means, I need to be able to check that when a user is within an edit page and decides to click on the website logo, I need to prompt the user that changes have been made and provide some message that has a "Leave page yes/no dialog"
Unsure what approach to take inorder to accomplish the above.
I have seen other threads within SO but they are using older versions of react-router-dom.
Any guidance would be great.
UPDATE: Code used but didn't seem to work:
useEffect(() => {
window.addEventListener('beforeunload', function (e) {
var confirmationMessage =
'It looks like you have been editing something. ' +
'If you leave before saving, your changes will be lost.';
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
});
}, []);
This demostrate a simplified version of a complex example to manually check for dirty forms, instead of relying on unload.
Considering you have a mechanism to check for dirty forms.
e.g.
const Component = ({ text }) => {
const [ edited, setEdited ] = useState(text)
const checkDirty = () => edited !== text
return (... my form codes here...)
}
One of the solution is to create a CustomLink component. (psuedo code)
const CustomLink = React.forwardRef(({ onClick: dirty, href }, ref) => (
const beforeHref = (e) => {
e.preventDefault();
if (typeof dirty == "function") {
if (!dirty()) {
return redirect(href)
} else {
if (confirm("Should we redirect!")) {
return redirect(href);
} else {
return null
}
}
}
redirect(href);
}
// you should probably use the link component here
return <a href={href} onClick={beforeHref} {...rest} />
));
Then in the page you can create a link like
<CustomLink href="/somepage" onClick={checkDirty} />
PS: Of course in the overall pages, you must pass those props to your menu, and your logo.

How to run a function when user clicks the back button, in React.js?

I looked around and tried to find a solution with React router.
With V5 you can use <Promt />.
I tried also to find a vanilla JavaScript solution, but nothing worked for me.
I use React router v6 and histroy is replaced with const navigate = useNavigation() which doesn't have a .listen attribute.
Further v6 doesn't have a <Promt /> component.
Nevertheless, at the end I used useEffect clear function. But this works for all changes of component. Also when going forward.
According to the react.js docs, "React performs the cleanup when the component unmounts."
useEffect(() => {
// If user clicks the back button run function
return resetValues();;
})
Currently the Prompt component (and usePrompt and useBlocker) isn't supported in react-router-dom#6 but the maintainers appear to have every intention reintroducing it in the future.
If you are simply wanting to run a function when a back navigation (POP action) occurs then a possible solution is to create a custom hook for it using the exported NavigationContext.
Example:
import { UNSAFE_NavigationContext } from "react-router-dom";
const useBackListener = (callback) => {
const navigator = useContext(UNSAFE_NavigationContext).navigator;
useEffect(() => {
const listener = ({ location, action }) => {
console.log("listener", { location, action });
if (action === "POP") {
callback({ location, action });
}
};
const unlisten = navigator.listen(listener);
return unlisten;
}, [callback, navigator]);
};
Usage:
useBackListener(({ location }) =>
console.log("Navigated Back", { location })
);
If using the UNSAFE_NavigationContext context is something you'd prefer to avoid then the alternative is to create a custom route that can use a custom history object (i.e. from createBrowserHistory) and use the normal history.listen. See my answer here for details.

load splash screen before nextjs

I have a NextJS website and I want to add a Splash Screen for before website is loaded
but because the Splash Screen is also in the NextJS code, it will loading when nextjs rendered on the server and the JS downloaded and executed on the client. in fact, it's useless because it will execute after the page is ready!
how can I do the Splash Screen before react completely loaded and executed ?
I also use nginx for proxy_pass
use this code
useEffect(() => {
const handleStart = () => { setPageLoading(true); };
const handleComplete = () => {
setPageLoading(false);
};
router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);
}, [router]);
and use pageLoding for show splash
For loading screen:
import React from 'react'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((res) => res.json());
// your main function
export default function Profile() {
//for relative and absolute paths
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
//for the loading you can create your custom component and insert instead of div, like this you keep same styling
if (!data) return <div>loading...</div>
if (data) return <div>hello {data.name}!</div>
}
Don't use useEffect hook, use this lib, better code and functionnality.
You have multiple possibility
You can start your Next Application on a page that contain a simple shimmer(splash screen), then replace the URL by url that contain SSR, and finally remove loading page for indexing with robot.txt. You can read more about this tips there.
You can insert inside on ... tag a CSS to show a loader(your splash screen). Then when the SSR function will be in loading the loader will be hide but when the browser download index.html(first file loaded, that contain style tag) the loader will be show before the browser download another assets(images, js, etc) and load it. You can read more about this tips there
The first tips will show loader fast than the second tip but require more steep(create another page, replace url, remove loader page for indexing)
You can do it by using DOMContentLoaded event, here an example:
In your _app.tsx and outside your function:
if (typeof window !== "undefined") {
document.addEventListener("DOMContentLoaded", () => {
// your code here ..
}
}

Redirect shows the sign in page partially before redirecting in next js react

I have a simple app where if a user is signed in and if the user manually tries to go to /signin page he is redirected to the index page. I'm using nextjs and to achieve this is I run a function in useEffect where boolean is checked true if so I use Router.push is used to redirect the user to the index page. The functionality works fine I see a few milliseconds of sign-in page before redirecting to the index page. Why is that? is it because the useEffect is called every time after the component is rendered? basically I want to run that code before rendering component and useEffect runs after the component is rendered. I want basically to run the code something like componentWillMount. How do I do it in the functional component? or is there any other solution?
const SigninComponent = () => {
useEffect(() => {
isAuth() && Router.push(`/`);
}, []);
return(
// components
);
}
Thats because useEffect will only run after component has mounted.
You can use different solutions :
Conditional rendering, add a loading field to state that is true by default, when it is true your component will render something like a spinner/loading component, if isAuth() return false then you will render something else (most likely a login form).
Pseudocode:
const SigninComponent = () => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if(isAuth()){
Router.push(`/`);
}else{
setLoading(false)
}
}, []);
if(!loading){
return <Login />
}else{
return <MyloaderComponent />
}
}
Use HOC component, similar to above but you will wrap the above logic in a HOC component
Use getServerSideProps and run isAuth() server-side instead that client-side, if you redirect inside getServerSideProps the component will not render at all. (you can use getServerSideProps only in pages)
export const getServerSideProps = async (ctx) => {
const auth = await isAuth() // or other any logic, like read cookies...
if (!auth) {
const { res } = ctx;
res.setHeader("location", "/");
res.statusCode = 302;
res.end();
return;
}
return {
props: {}, // or return user
};
};
You may consider using Redirect Component
const SigninComponent = () => {
if (isAuth()) return <Redirect to="/" />
return(
// components
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Custom leave dialog in react-router

I have a form where the user can edit stuff. If the user has some unsaved changed and wants to leave the page I want to give him a custom dialog, asking him if he is sure. If then the navigation should continue and the form changed flag should be reset, otherwise the user should stay at the page.
Here you can check my current higher order component which is checking this condition. It is working, but the function leave returns a string and react-router will then display that text in a native alert.
Is there a way that I can show my custom dialog in here? And can I also get a callback or similar of the alert, so that I can dispatch an event which tells the storage, that the latest changes are not important.
import React, {Component, PropTypes} from "react";
import connectRouter from "./connectRouter";
import { connect } from "react-redux";
import {injectIntl, intlShape} from "react-intl";
export default function confirmLeave(RouteTargetComponent) {
#injectIntl
#connectRouter
#connect((state, routing) => {
return {
studies: state.studies
};
})
class ConfirmLeaveHOC extends Component { // HOC = Higher Order Component
static propTypes = {
router: PropTypes.object.isRequired,
route: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
intl: intlShape.isRequired,
studies: PropTypes.object.isRequired,
};
leave = () => {
if (this.props.studies.isChanged) {
// lets stop the navigation
return this.props.intl.formatMessage({ id: "confirmLeave" });
}
// continue the navigation
return true;
}
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, this.leave.bind(this));
}
render() {
// render the component that requires auth (passed to this wrapper)
return (<RouteTargetComponent {...this.props}/>);
}
}
return ConfirmLeaveHOC;
}
Since customizing browser dialogs is not possible, you'll have to render a separate component (e.g bootstrap modal) and use a callback to determine which button was clicked, and what action to take.
I actually ran into the same problem you're facing very recently, and I was able to solve it by using routerWillLeave and using callbacks from another component.
Form component
routerWillLeave = (route) => {
if (!this.waitingForConfirm && this._hasUnsavedChanges() && !this.clickedSave) {
this.refs.confirmAlert._show( ((v) => {
if (v) {
this.context.router.push(route.pathname);
}
this.waitingForConfirm = false;
}).bind(this));
this.waitingForConfirm = true;
return false;
}
}
The implementation of customized dialog like this one is unfortunately quite a pain in the back. I had to use 3 variables here to correctly control the desired behavior:
waitingForConfirm - necessary to prevent the logic from running a second time when the user confirms to navigate out. Specifically, when the callback is run and we do this.context.router.push(route.pathname), the routerWillLeave will run again(!), but since we've already confirmed navigation we must prevent this logic from running again.
_hasUnsavedChanges() - checks if any input fields have changed (no reason to ask if there's no changes to be saved).
clickedSave - don't ask for confirmation if the user clicked Save - we know we want to leave.
Dialog component
_show = (callback) => {
this.callback = callback;
this.setState({show: true});
}
_hide = () => {
this.setState({show: false});
this.callback = null;
}
_dialogAction = (input) => {
if (this.callback) {
this.callback(input);
}
this._hide();
}
render() {
return (
...
<Button onClick={this._dialogAction.bind(this, true)}>Yes</Button>
<Button onClick={this._dialogAction.bind(this, false)}>No</Button>
);
}
Obviously, you'll have to customize the above snippets to fit your application, but hopefully it will provide some insight into how to solve the problem.
A less complicated and more forward way is using a setRouteLeaveHook on your component. In accordance to the react-router v2.4.0
import React from 'react'
import { withRouter } from 'react-router'
const Page = React.createClass({
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, () => {
if (this.state.unsaved)
return 'You have unsaved information, are you sure you want to
leave this page?'
})
}
render() {
return Stuff
}
})
export default withRouter(Page)
Here is the React-router version on their github page

Resources