React router prevent route change and close popup on back button
Asked Answered
P

2

2

In my react app I have a popup in one of the page. Now when the popup is open and the user presses browser's back button then user is taken to the previous page, instead I want to simply close the popup.

I have followed a lot of solutions related to this but none seems to work. The below code allows me to close the popup on back but what it does is push an extra url in history of the page in which popup is present. Hence once popup is closed I need to press back twice to actually get to previous page. This gives a bad user experience hence I wont want to add it to my app.

const myPopState = (event) => {
    window.history.go(1);
    closeModal();
};

useEffect(() => {
    window.history.pushState(null, null, location.href);
    window.addEventListener("popstate", myPopState);
    return () =>  window.removeEventListener("popstate", myPopState);
}, []);

PS: I dont want to have a separate route for the popup screen. There is a similar question asked already but I am not willing to put in a unique route for popup so asking this.

Peta answered 1/9, 2020 at 12:36 Comment(0)
N
0

Live Demo

Create this function file first:

import { useEffect, useState } from 'react'
import _ from 'lodash'

const listOfListener = {}

function onPopState(event) {
  _.forEach(listOfListener, (l) => {
    tryIt(() => l(event))
  })
}

export const tryIt = (fun, defaultVal) => {
  try {
    return fun()
  } catch (e) {
    return _.isFunction(defaultVal) ? defaultVal() : defaultVal
  }
}

export const getSafe = (fun, defaultVal) => tryIt(fun, defaultVal)

const buttonDelay = 1000

export default function useOpenWithBrowserHistory(uniq, defaultValue) {
  const [openDelay, setOpenDelay] = useState(false)
  const [closeDelay, setCloseDelay] = useState(false)
  const [open, setOpen] = useState(false)
  const [element, setElement] = useState(undefined)

  useEffect(() => {
    if (!listOfListener[uniq]) {
      listOfListener[uniq] = function (event) {
        const state = event.state
        const data = getSafe(() => state[uniq], undefined)
        if (!state || data !== true) {
          setOpen(false)
          return
        }
        if (data === true) {
          setOpen(true)
        }
      }
    }
    window.onpopstate = onPopState
    if (defaultValue) {
      handleOpenClick()
    }

    return () => {
      tryIt(() => {
        delete listOfListener[uniq]
      })
    }
  }, [])

  const handleOpenClick = (open = true) => {
    if (openDelay === true) return
    setOpenDelay(true)

    const openProcess = () => {
      const data = {}
      data[uniq] = true
      // eslint-disable-next-line no-undef
      history.pushState(data, null, location.href)
      setOpen(true)

      setTimeout(() => {
        setOpenDelay(false)
      }, buttonDelay)
    }

    const target = tryIt(() => open.target)

    if (target) {
      setElement(target)
      scrollToElement(target)
      setTimeout(() => {
        openProcess()
      }, 600)
      return
    }
    openProcess()
  }

  const handleCloseClick = () => {
    if (closeDelay === true) return
    setCloseDelay(true)
    if (element) {
      window.onscroll = () => {
        scrollToElement()
      }
      setTimeout(() => {
        window.onscroll = () => {}
        setElement(undefined)
      }, 1000)
    }
    window.history.back()

    setTimeout(() => {
      setCloseDelay(false)
    }, buttonDelay)
  }

  function scrollToElement(target = element) {
    tryIt(() =>
      target.scrollIntoView({
        behavior: 'smooth',
        block: 'center',
        inline: 'nearest'
      })
    )
  }

  const handleSetOpen = (open) => {
    if (open) {
      handleOpenClick(open)
      return
    }
    handleCloseClick()
  }

  return [open, handleSetOpen, handleOpenClick, handleCloseClick]
}

and then in your component you can use like this:

function App() {
    //handleOpen and handleClose it's direct handler
    const [open, setOpen, handleOpen, handleClose] = useOpenWithBrowserHistory(
        "uniq-key"
    );
    return (
        <div className="App">
            <button onClick={handleOpen}>open</button>
            {open && (
                <div class="modal">
                    You can close this modal with browser back button or click
                    Close button
                    <br />
                    <br />
                    <button onClick={handleClose}> close</button>
                </div>
            )}
        </div>
    );
}

Or you can use useOpenWithBrowserHistory function in this library reacthelper

Live Demo

Narco answered 2/6, 2022 at 11:0 Comment(2)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewPersian
@Persian you can check againNarco
A
-1

Keep the popup as a separate component and import it in whichever file you want. Then simply use the state to manage hide/display of the popup box.

check out my answer to this question.

https://mcmap.net/q/1330638/-how-to-show-modal-box-on-back-button-in-react-router-v4

Abraxas answered 30/3, 2021 at 15:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.