npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@allpro/react-router-pause

v1.1.3

Published

Helper for React Router to provide customizable asynchronous navigation blocking

Downloads

2,297

Readme

React Router Pause (Async)

npm package gzip-size install-size build coverage license donate

React-Router-Pause ("RRP") is a Javascript utility for React Router v4 & v5. It provides a simple way to asynchronously delay (pause) router navigation events triggered by the user. For example, if a user clicks a link while in the middle of a process, and they will lose data if navigation continues.

For more detail, see: Control React Router, Asynchronously

RRP is similar to:

Motivation

The standard React Router Prompt component is synchronous by default, so can display ONLY window.prompt() messages. The same applies when using router.history.block.

The window.prompt() dialog is relatively ugly and cannot be customized. They are inconsistent with the attractive dialogs most modern apps use. The motivation for RRP was it overcome this limitation.

It is possible to have an asychronous dialog by customizing createHistory.getUserConfirmation(). However this is clumsy and allows only a single, global configuration.

Advantages of RRP

  • Useful for anything async; not just 'prompt messages'.
  • Very easy to add asynchronous navigation blocking.
  • Fully customizable by each component - no limitations.
  • Does not require modifying the history object.
  • Is compatible with React Native and server-side-rendering.

Live Example

Try the demo at: https://allpro.github.io/react-router-pause

Play with the demo code at: https://codesandbox.io/s/github/allpro/react-router-pause/tree/master/example

If you pull or fork the repo, you can run the demo like this:

  • In the root folder, run npm start
  • In a second terminal, in the /example folder, run npm start
  • The demo will start at http://localhost:3000
  • Changes to the component or the demo will auto-update the browser

Installation

  • NPM: npm install @allpro/react-router-pause
  • Yarn: yarn add @allpro/react-router-pause
  • CDN: Exposed global is ReactRouterPause
    • Unpkg: <script src="https://unpkg.com/@allpro/react-router-pause/umd/react-router-pause.min.js"></script>
    • JSDelivr: <script src="https://cdn.jsdelivr.net/npm/@allpro/react-router-pause/umd/react-router-pause.min.js"></script>

Compatibility

RRP is designed for maximum backwards compatibility. It's a React class-component that utilizes the withRouter() HOC provided by React-Router 4+. RRP does not hack the router context or use any non-standard trickery that might cause compatibility issues in the future.

Peer-Dependencies

RRP will work in any project using React-Router 4.x or 5.x, which requires React >=15.

"peerDependencies": {
    "prop-types": ">=15",
    "react": ">=15",
    "react-dom": ">=15",
    "react-router-dom": ">=4"
}

React-Hooks Testing Version

There is also a version of RRP using React-hooks. This is not exported because it requires React 16.8 or higher, so is not compatible with older projects. This version is in the repo for anyone interested: https://github.com/allpro/react-router-pause/blob/master/src/ReactRouterPauseHooks.js

When React-Router is eventually updated to provide React-hooks, the RRP hooks-version will be updated to take advantage of this. It may become the recommended version for projects using the updated React-Router.

Usage

RRP is a React component, but does NOT render any output. RRP also does NOT display any prompts itself. It only provides a way for your code to hook into and control the router.

Component Properties

The RRP component accepts 3 props:

  • handler   {function} [null]   optional This is called each time a navigation event occurs. If a handler is not provided, RRP is disabled. See handler Function below.

  • when   {boolean} [true]   optional Set when={false} to temporarily disable the RRP component. This is an alternative to using conditional rendering.

  • config   {object} [{}]   optional A configuration object to change RRP logic.

    • config.allowBookmarks   {boolean} [true] Should bookmark-links for same page always be allowed? If false, bookmark-links are treated the same as page-links.
Example
<ReactRouterPause 
    handler={ handleNavigationAttempt }
    when={ isFormDirty }
    config={{ allowBookmarks: false }}
/>

handler Function

The function set in props.handler will be called before the router changes the location (URL).

Three arguments are passed to the handler:

  • navigation   {object} An API that provides control of the navigation. See navigation API Methods" below.

  • location   {object} A React Router location object that describes the navigation event.

  • action   {string} The event-action type: PUSH, REPLACE, or POP

navigation API Methods

The navigation API passed to the handler has these methods:

  • navigation.isPaused() Returns true or false to indicate if a navigation event is currently paused.

  • navigation.pausedLocation() Returns the location object representing the paused navigation, or null if no event is paused.

  • navigation.pause() Pause navigation event - equivalent to returning null from the handler. Note: This must be called before the handler returns.

  • navigation.resume() Triggers the 'paused' navigation event to occur.

  • navigation.cancel() - Clears 'paused' navigation so it can no longer be resumed. After cancelling, navigation.isPaused() will return false. NOTE: It is usually not necessary to call navigation.cancel().

  • navigation.push(path, state) The router.history.push() method; allows redirecting a user to an alternate location.

  • navigation.replace(path, state) The router.history.replace() method; allows redirecting a user to an alternate location.

handler Function Return Values

If the handler does NOT call any navigationAPI method is before it returns, then it must return one of these responses:

  • true or undefined - Allow navigation to continue.
  • false - Cancel the navigation event, permanently.
  • null - Pause navigation so can optionally be resumed later.
  • Promise - Pause navigation until promise is settled, then:
    • If promise is rejected, cancel navigation
    • If promise resolves with a value of false, cancel navigation
    • If promise resolves with any other value, resume navigation

This example pauses navigation, then resumes after 10 seconds.

function handleNavigationAttempt( navigation, location, action ) {
	setTimeout( navigation.resume, 10000 ) // RESUME after 10 seconds
	return null // null means PAUSE navigation
}

The example below returns a promise to pause navigation while validating data asynchronously. If the promise resolves, navigation will resume unless false is returned by promise. If the promise rejects, navigation is cancelled.

function handleNavigationAttempt( navigation, location, action ) {
	return verifySomething(data)
	    .then(isValid => {
	    	if (!isValid) {
	    		showErrorMessage()
	    		return false // Cancel Navigation
	    	}
	    	// Navigation resumes if 'false' not returned, and not 'rejected'
	    })
}

Same-Location Blocking

RRP automatically blocks navigation if the new location is the same as the current location. This prevents scenarios where React Router reloads a form when the user clicks the same page-link again.

The comparison between two locations includes:

  • pathname ("https://domain.com/section/page.html")
  • search ("?key=value&otherValues")
  • state ("value" or { foo: 'bar' })

The location 'hash' (bookmark) is ignored by default. See config.allowBookmarks in the Component Properties section.

Implementation

A common requirement in an app is to ask a user if they wants to 'abort' a process, (such as filling out a form), when they click a navigation link.

Below are 2 examples using a custom 'confirmation dialog', showing different ways to integrate RRP with your code.

Functional Component Example

This example keeps all code inside the handler function, where it has access to the navigation methods. The setState hook is used to store and pass handlers to a confirmation dialog.

import React, { Fragment } from 'react'
import { useFormManager } from '@allpro/form-manager'
import ReactRouterPause from '@allpro/react-router-pause'

import MyCustomDialog from './MyCustomDialog'

// Functional Component using setState Hook
function myFormComponent( props ) {
    // Sample form handler so can check form.isDirty()
    const form = useFormManager( formConfig, props.data )
    
    const [ dialogProps, setDialogProps ] = useState({ open: false })
    const closeDialog = () => setDialogProps({ open: false })

    function handleNavigationAttempt( navigation, location, action ) {
        setDialogProps({
            open: true,
            handleStay: () => { closeDialog(); navigation.cancel() },
            handleLeave: () => { closeDialog(); navigation.resume() },
            handleHelp: () => { closeDialog(); navigation.push('/form-help') }
        })
        // Return null to 'pause' and save the route so can 'resume'
        return null
    }

    return (
        <Fragment>
             <ReactRouterPause 
                 handler={handleNavigationAttempt}
                 when={form.isDirty()}
             />
        
             <MyCustomDialog {...dialogProps}>
                 If you leave this page, your data will be lost.
                 Are you sure you want to leave?
             </MyCustomDialog>
        
        ...
        </Fragment>
    )
}

Class Component Example

In this example, the navigation API object is assigned to a property so it is accessible to every method in the class.

import React, { Fragment } from 'react'
import FormManager from '@allpro/form-manager'
import ReactRouterPause from '@allpro/react-router-pause'

import MyCustomDialog from './MyCustomDialog'

// Functional Component using setState Hook
class myFormComponent extends React.Component {
    constructor(props) {
        super(props)
        this.form = FormManager(this, formConfig, props.data)
        this.state = { showDialog: false }
        this.navigation = null
    }

    handleNavigationAttempt( navigation, location, action ) {
        this.navigation = navigation
        this.setState({ showDialog: true })
        // Return null to 'pause' and save the route so can 'resume'
        return null
    }
    
    closeDialog() {
        this.setState({ showDialog: false })
    }
    
    handleStay() {
        this.closeDialog()
        this.navigation.cancel()
    }
    
    handleLeave() {
        this.closeDialog()
        this.navigation.resume()
   }
    
    handleShowHelp() {
        this.closeDialog()
        this.navigation.push('/form-help')
    }

    render() {
        return (
            <Fragment>
                <ReactRouterPause 
                    handler={this.handleNavigationAttempt}
                    when={this.form.isDirty()}
                />
        
                {this.state.showDialog &&
                    <MyCustomDialog
                         onClickStay={this.handleStay}
                         onClickLeave={this.handleLeave}
                         onClickHelp={this.handleShowHelp}
                    >
                        If you leave this page, your data will be lost.
                        Are you sure you want to leave?
                    </MyCustomDialog>
                }
            ...
            </Fragment>
        )
    }
}

Built With

Contributing

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.

Versioning

We use SemVer for versioning. For the versions available, see the tags on this repository.

License

MIT © allpro See LICENSE file for details