问题
I'm working in a SPA in React that doesn't use React Router to create any Routes; I don't need to allow users to navigate to specific pages. (Think multi-page questionnaire, to be filled in sequentially.)
But, when users press the back button on the browser, I don't want them to exit the whole app; I want to be able to fire a function when the user presses the back button that simply renders the page as it was before their last selection. (Each page is a component, and they're assembled in an array, tracked by a currentPage
state variable (from a state hook), so I can simply render the pages[currentPage -1]
.
Using (if necessary) the current version of React Router (v5), function components, and Typescript, how can I access the back-button event to both disable it and replace it with my own function?
(Other answers I've found either use class components, functions specific to old versions of React Router, or specific frameworks, like Next.js.)
Any and all insight is appreciated.
回答1:
After way too many hours of work, I found a solution. As it ultimately was not that difficult once I found the proper path, I'm posting my solution in the hope it may save others time.
- Install React Router for web, and types -
npm install --save react-router-dom @types/react-router-dom
. - Import
{ BrowserRouter, Route, RouteComponentProps, withRouter }
fromreact-router-dom
. - Identify the component whose state will change when the back button is pressed.
- Pass in
history
fromRouteComponentProps
via destructuring:function MyComponent( { history }: ReactComponentProps) { ... }
On screen state change (what the user would perceive as a new page) add a blank entry to
history
; for example:function MyComponent( { history }: ReactComponentProps) { const handleClick() { history.push('') } }
This creates a blank entry in history, so history has entries that the back button can access; but the url the user sees won't change.
- Handle the changes that should happen when the back-button on the browser is pressed. (Note that component lifecycle methods, like
componentDidMount
, won't work in a function component. Make sureuseEffect
is imported fromreact
.)useEffect(() => { // code here would fire when the page loads, equivalent to `componentDidMount`. return () => { // code after the return is equivalent to `componentWillUnmount` if (history.action === "POP") { // handle any state changes necessary to set the screen display back one page. } } })
- Wrap it in
withRouter
and create a new component with access to a Route's properties:const ComponentWithHistory = withRouter(MyComponent);
Now wrap it all in a
<BrowserRouter />
and a<Route />
so that React Router recognizes it as a router, and route all paths topath="/"
, which will catch all routes unless Routes with more specific paths are specified (which will all be the same anyway, with this setup, due tohistory.push("")
; the history will look like["", "", "", "", ""]
).function App() { return ( <BrowserRouter> <Route path="/"> <ThemeProvider theme={theme}> <ComponentWithHistory /> </ThemeProvider> </Route> </BrowserRouter> ); } export default App;
A full example now looks something like this:
function MyComponent( { history }: ReactComponentProps) { // use a state hook to manage a "page" state variable const [page, setPage] = React.useState(0) const handleClick() { setPage(page + 1); history.push(''); } useEffect(() => { return () => { if (history.action === "POP") { // set state back one to render previous "page" (state) setPage(page - 1) } } }) } const ComponentWithHistory = withRouter(MyComponent); function App() { return ( <BrowserRouter> <Route path="/"> <ThemeProvider theme={theme}> <ComponentWithHistory /> </ThemeProvider> </Route> </BrowserRouter> ); } export default App;
If there are better ways, I would love to hear; but this is working very well for me.
来源:https://stackoverflow.com/questions/61939358/how-to-intercept-back-button-in-a-react-spa-using-function-components-and-react