1

I have a simple page transition that gets called when clicking a link. The new page animates from the bottom of the screen upwards to the top. It works fine, but it's also working when you click the back button in the browser. I don't want the transition to fire if someone clicks the back button on the browser, I just want to go back to the page at whatever scrolled position I left it in, in exactly the same way you would expect to happen by clicking the browser back button.

If I take the CSS transitions off then the back button works fine, so I need to somehow differentiate between the back button being clicked and a link being clicked and then play the transition accordingly.

I don't know if I need to setState or something else? I did try that, but it seemed to set the state variable after the transition had happened.

import React, { useEffect, useState }  from 'react';
import {Route, NavLink, Switch, useHistory } from "react-router-dom";
import './App.css';
import Home from './Home';
import About from './About';
import { CSSTransition, TransitionGroup,} from 'react-transition-group';
            
            
 const usePageMeta = (title, description) =>{
       const defaultTitle = "app-name";
       const defaultDesc = "meta description";
            
       useEffect(() => {
             document.title = title || defaultTitle;
                     document.querySelector("meta[name='description']").setAttribute("content", description || defaultDesc);
                }, [defaultTitl
    
        e, title, defaultDesc, description]);
            };
            
  const App = () =>{
            
            
                return (
                    <div className="App">
                        <div className="nav fixed top-0 left-0 z-10 w-full">
                            <NavLink exact to="/" activeClassName="active">Home</NavLink>
                            <NavLink to="/about"  activeClassName="active">About</NavLink>
        
        
                    </div>
        
        
                        <Route render={({location}) => (
        
        
                            <TransitionGroup>
                                <CSSTransition
                                    key={location.key}
                                    timeout={1000}
                                    classNames="fade"
        
                                >
                                    <Switch location={location}>
                                        <Route exact path="/">
                                            <Home usePageMeta={usePageMeta}/>
                                        </Route>
                                        <Route path="/about">
                                            <About usePageMeta={usePageMeta}/>
                                        </Route>
                                    </Switch>
                                </CSSTransition>
                            </TransitionGroup>
        
        
                        )} />
        
        
                </div>
        
        
            );
        
        
        }
        
        export default App;

Can anyone point me in the right direction please?

cannon303
  • 403
  • 2
  • 12
  • I guess the transition is going into the history of the page and hence triggering a animation back to the original position. Can you replace the history with `useNavigate` hook and set the **replace** option true ? You can do the same with the history object. If not, a little more insight on how the transition is happening would be of great help. – Cpreet Jul 31 '22 at 09:11
  • Unfortunately I cannot use useNavigate. I'm using react-router-dom ^5.2.0 and I'm using react ^17.0.2. I cannot use later versions because somewhere along the lines Switch is not supported and the page transitions no longer work and then I'm back to square one. I have updated my question with the full code to give a little more insight. – cannon303 Jul 31 '22 at 15:49

1 Answers1

0

After some testing, I can see that this is bound to happen as the path is being loaded into the history of the browser.

tl;dr This is not a quirk of the library, its just the way the browsers work. The current session always pushes state on the history object. To prevent this you can do two things

  • Overwrite the history state whenever the user navigates to a new path.
  • Prevent the default action of the back button and redirect to the entry site.

Solution 1:

  1. import createBrowserHistory from 'history' package and make a mutable history outside the app component.
  2. make a immutable string of the start state when entering the app with history.createHref(history.location)
  3. define an onClick handler which replaces the histories's top state to current location.
  4. register a click handler on both NavLinks with the above defined function.
  5. add a listener to the history (history.listen) and call history.goBack() if the action is pop.
  6. call unlisten on unmount.
import { createBrowserHistory } from 'history';

let history = createBrowserHistory();

const ContainerComponent = () => {
  // removed uselocation and usehistory hooks
  
  const location = history.createHref(history.location);
  
  const replaceHistory = () => {
    history.replace(location);
  }

  const unlisten = history.listen((location, action) => {
    if (action == "POP")
      history.goBack();
  })

  useEffect(() => {
    return () => unlisten();
  })

  return (
    <div>
      <Navlink to='/' onClick={replaceHistory} />
      <Navlink to='/about' onClick={replaceHistory} />
    </div>
  );
}

Solution 2:

  1. import createBrowserHistory from the 'history' module and create a mutable history object outside the app component.
  2. add a listener to the history (history.listen) which goes back '-1' number of pages when it recives the "POP" action.
  3. useEffect to 'unlisten' this event.

Note: this is assuming the user has to be navigated to the site which came from.

import { createBrowserHistory } from 'history';

// Creates a mutable history state.
let history = createBrowserHistory();

const ContainerComponent = () => {

  //removed the eventlistener on window
  
  const unlisten() = history.listen((location, action) => {
    //remove the logging once satisfied with the result.
    console.log(`${location}, ${action}`);
    if ( action == "POP" ) {
      history.goBack();
    }
  })
  
  // removed 'popstate' evenhandler
  
  // adding useEffect to call unlisten on unmount
  useEffect (() => {
    return () => unlisten();
  }, [])

  const count = () => {
    setPageChanges(pageChanges + 1);
  }

  return (
    <div>
      <NavLink to='/' onClick={count} />
      <NavLink to='/about' onClick={count} />
    </div>
    {/*... rest of the switch code with the transition ...*/}
  );
}

Edit: Solution 2 is refactored to use the 'history' package utilities. This is seen more appropriate as the browser states are not touched with react code.

Also in the docs, it was mentioned that the history object is mutable which does result in some problems with mounting.

Caveat: this solution goes through all the states in between where the user is and where they started from, so expect some flashes of the earlier components.

Solution 1 also uses the same package, with the caveat that it will go to the first page and then goBack() 1 page more. This will be less of a flash then solution 2.

Cpreet
  • 148
  • 1
  • 8
  • unfortunately Solution 1 has no effect and Solution 2 just throws up errors around const useEffect... – cannon303 Aug 01 '22 at 00:12
  • Yup now it works with all the tricks that could be used. I do suspect there is a simpler solution, but that would pertain to diving into the history stack's construction. Some Excercise for the reader – Cpreet Aug 01 '22 at 14:14
  • History will not install, no matter what version I try. It's time to reconsider what I'm doing I think. 10 days trying to get some simple page transitions working with react. Something like this shouldn't be rocket science. It's just too flaky. – cannon303 Aug 01 '22 at 15:37
  • I know, its just the version difference, the flaky part is there. Just a thing that i may have forgotten to mention, the history package installa with react router :P. Go ahead and import it, of using typescript (recommend), install @types/react-router – Cpreet Aug 01 '22 at 15:44
  • yeah it really doesn't work I'm afraid. – cannon303 Aug 02 '22 at 19:19