How to create a progress bar in Next.js? - reactjs

I'm trying to create a Progress Bar which is at the very top of the website and each time you load the page, you see that Bar filling up.
I want to implement the same functionality in Next.js, but I'm struggling to do so. There are many libraries for the loading bar, but they all ask for a value prop which basically indicates how much the bar is filled, but I cannot find a way to get this value prop dynamically. I don't want to hardcode it, I want it to fill up as the page gets loaded.
Is there any way that I can find the value of the value prop dynamically?

There are a couple ways of doing that, nextjs have the Router import, so you can use the events see here
With that in mind you could create the visual component and if you really want to show on every page change, you could use this with your _app.tsx/jsx with the useEffect hook. Just don't forget to cleanup the events after listening to them
This answer does a great job showing it how to implement it, it's a great reference and very generic, so you can custom as much as you want

In __app.js or __app.tsx
import Router from 'next/router';
import NProgress from 'nprogress';
add the CSS for NProgress in the head:
<link rel="stylesheet" type="text/css" href="/nprogress.css" />
Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());

Related

How can i write a test using React Testing Library to check if hover changes the components style?

I am testing my application, and encountered a problem. When trying to test whether a row in my Dropdown component applies an effect on hover, I noticed I was not able to check elements for their background color, which I find odd.
Trying to use the jest-dom matcher "toHaveStyle()", the following is an example where I cannot for the life of me get it to work.
dropdown.test.tsx
test('Should contain clickable elements that change style when hovered', () => {
const dropElement1 = screen.getByLabelText('testLabel1');
expect(dropElement1).toHaveStyle('background: white');
});
Error
I have also tried this by using 'background-color', by using the hex value (another interesting bug is that PrettyDom converts hex to RGB), or by adding ; to the declaration in toHaveStyle().
I am certain that the element is indeed white, and I can't understand what is going wrong. If my approach is bad practice and you have a better idea of how to check this, or you have a solution to my problem, please, let me know!
Your testing case can't find the dropElement1 styles because it's a drop-down menu and not opened since you just render the Dropdonw component.
You need to simulate a mouse hover or clicking action on the DropDown menu and then expect to have styles property for it.
import React from "react";
import { render, screen, fireEvent, waitFor } from "#testing-library/react";
import { Dropdown } from "./Dropdown";
test('Should contain clickable elements that change style when hovered', async () => {
render(<Dropdown />);
fireEvent.mouseEnter(screen.getByText('drop-down-btn'));
await waitFor(() => screen.getByTestId('dropdown-menu'))
expect(screen.getByLabelText('testLabel1')).toHaveStyle('background: white');
});
Note: as you have not posted the Dropdown component, I put some sample names for getting your toggles and drop-down menu. also, you can read about the mouse events on the react-testing-library. you can also use mouseOver but it depends on your drop-down menu implementation.

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

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.

React + Material Design Lite - How to close navigation drawer when menu link is clicked?

so I am unsure of how to best go about closing the MDL drawer when a link is clicked inside the drawer. From similar questions I have gathered you simply need to remove the is-active class from both the div with a class of mdl-layout__obfuscator, and the div with a class of mdl-layout__drawer.
What is the best way to go about this in React? I am not using React-MDL by the way, I am using the CDN version if that matters or helps...
Sorry I am new to both React and MDL.
In most of the cases, we can use react state to handle the show/hide the component. But in this case, we cannot do as we dont have access to have state in react-mdl inbuilt component. So i would suggest to manipulate the react-mdl component.
What manipulate DOM? :O Really?
Yes. We are not doing DOM manipulation for our code, we are just manipulating react-mdl so I think this should not be a issue.
class Navbar extends Component {
hideToggle() {
var selectorId = document.querySelector('.mdl-layout');
selectorId.MaterialLayout.toggleDrawer();
}
render() {
return (
<Navigation>
<Link to={routes.XXX} onClick={() => this.hideToggle()}> XXX</Link>
</Navigation>
);
}
This should solve your issue.
Ref: Github source
Thank you.

How to prevent route change using react-router

There's a certain page in my React app that I would like to prevent the user from leaving if the form is dirty.
In my react-routes, I am using the onLeave prop like this:
<Route path="dependent" component={DependentDetails} onLeave={checkForm}/>
And my onLeave is:
const checkForm = (nextState, replace, cb) => {
if (form.IsDirty) {
console.log('Leaving so soon?');
// I would like to stay on the same page somehow...
}
};
Is there a way to prevent the new route from firing and keep the user on the same page?
It is too late but according to the React Router Documentation you can use preventing transition with helping of <prompt> component.
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
if isBlocking equal to true it shows a message. for more information you can read the documentation.
I think the recommended approach has changed since Lazarev's answer, since his linked example is no longer currently in the examples folder. Instead, I think you should follow this example by defining:
componentWillMount() {
this.props.router.setRouteLeaveHook(
this.props.route,
this.routerWillLeave
)
},
And then define routerWillLeave to be a function that returns a string which will appear in a confirmation alert.
UPDATE
The previous link is now outdated and unavailable. In newer versions of React Router it appears there is a new component Prompt that can be used to cancel/control navigation. See this example
react-router v6 no longer supports the Prompt component (they say that they hope to add it back once they have an acceptable implementation). However, react-router makes use of the history package which offers the following example for how to block transitions.
Note that to actually make this work in react router you have to replace the createBrowserHistory call with some hackery to make sure you are using the same history object as react router (see bottom of answer).
const history = createBrowserHistory();
let unblock = history.block((tx) => {
// Navigation was blocked! Let's show a confirmation dialog
// so the user can decide if they actually want to navigate
// away and discard changes they've made in the current page.
let url = tx.location.pathname;
if (window.confirm(`Are you sure you want to go to ${url}?`)) {
// Unblock the navigation.
unblock();
// Retry the transition.
tx.retry();
}
You'll need to put this inside the appropriate useEffect hook and build the rest of the functionality that would have otherwise been provided by prompt. Note that this will also produce an (uncustomizable) warning if the user tries to navigate away but closing the tab or refreshing the page indicating that unsaved work may not be saved.
Please read the linked page as there are some drawbacks to using this functionality. Specifically, it adds an event listener to the beforeunload event which makes the page ineligable for the bfcache in firefox (though the code attempts to deregister the handler if the navigation is cancelled I'm not sure this restores salvageable status) I presume it's these issues which caused react-router to disable the Prompt component.
WARING to access history in reactrouter 6 you need to follow something like the instructions here which is a bit of a hack. Initially, I assumed that you could just use createBrowserHistory to access the history object as that code is illustrated in the react router documentation but (a bit confusingly imo) it was intended only to illustrate the idea of what the history does.
We're using React Router V5, and our site needed a custom prompt message to show up, and this medium article helped me understand how that was possible
TLDR: the <Prompt/> component from react-router-dom can accept a function as the message prop, and if that function returns true you'll continue in the navigation, and if false the navigation will be blocked
React-router api provides a Transition object for such cases, you can create a hook in a willTransitionTo lifecycle method of the component, you are using. Something like (code taken from react-router examples on the github):
var Form = React.createClass({
mixins: [ Router.Navigation ],
statics: {
willTransitionFrom: function (transition, element) {
if (element.refs.userInput.getDOMNode().value !== '') {
if (!confirm('You have unsaved information, are you sure you want to leave this page?')) {
transition.abort();
}
}
}
},
handleSubmit: function (event) {
event.preventDefault();
this.refs.userInput.getDOMNode().value = '';
this.transitionTo('/');
},
render: function () {
return (
<div>
<form onSubmit={this.handleSubmit}>
<p>Click the dashboard link with text in the input.</p>
<input type="text" ref="userInput" defaultValue="ohai" />
<button type="submit">Go</button>
</form>
</div>
);
}
});

Resources