Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

Hashchange causes full rerender of home route #536

Open
nolanlawson opened this issue Dec 17, 2018 · 7 comments
Open

Hashchange causes full rerender of home route #536

nolanlawson opened this issue Dec 17, 2018 · 7 comments
Labels

Comments

@nolanlawson
Copy link
Contributor

I've been working on implementing modals in Pinafore that respect the browser back button (nolanlawson/pinafore#60). As it turns out, though, I think I've run into a bug in Sapper that makes this hard to implement.

Steps to repro:

  1. Go to home route (/)
  2. Open a "modal" via a hashchange (e.g. location.hash = '#my-modal')
  3. Press the back button

Expected result: the route doesn't re-render

Actual result: the route re-renders

Curiously, this only happens on the home route, not any other route. I've confirmed this by making a minimal repro in sapper-template.

If you open that demo, then open the console, then open the "modal," then press the back button, it calls oncreate for that route. But if you do the same on the "About" page, then oncreate is not called again.

@nolanlawson
Copy link
Contributor Author

Just to confirm, this bug still repros in Sapper 0.22.10. (I was hoping #484 would fix it.)

@nolanlawson
Copy link
Contributor Author

My bad, I should have tested v0.25.0. After testing that version, it does appear that #484 makes the behavior uniform across all routes, although unfortunately in favor of a re-render, which is not what I would want for a modal dialog. 😞

In any case, I think this bug can be closed because the behavior is at least consistent now. I'm still not sure how to implement a modal that respects the back button in Sapper, though.

@Rich-Harris
Copy link
Member

I'm also not sure what the solution is but I agree that this is a problem, so I'm going to reopen this so we can revisit it after the Svelte 3 version (which takes a different approach to #484, but also wouldn't fix this)

@Rich-Harris Rich-Harris reopened this Feb 4, 2019
@nolanlawson
Copy link
Contributor Author

In nolanlawson@cb21193 I have a little hack I'm playing around with to see if it can work properly, but I have no idea if this is the right approach. Ideally I would like it to never re-render when the route is the same, but maybe this is because I'm not using preload.

@nolanlawson
Copy link
Contributor Author

FWIW I am working around this currently with nolanlawson@0780b25, but the change is pretty hacky, and also isn't compatible with the current Svelte@3 changes, so I didn't open a PR.

@ramiroaisen
Copy link

ramiroaisen commented Sep 4, 2019

Simple snippet to put in client.js to avoid this

document.addEventListener('click', (event) => {
	if(event.target.tagName.toLowerCase() === 'a'){
		const href = event.target.getAttribute('href');
		if(href && href[0] === '#'){
			event.preventDefault();
			document.location.hash = href;
		}
	}
}, true)

Also i let you a store for window.location.hash, note that it will work in browser and fallback to an empty hash ("") in server

import {readable} from "svelte/store";

const hash = readable("", 
  (set) => {
    if(process.browser){
      const fn = () => set(window.location.hash.replace(/^#+/g, ''));
      fn();
      window.addEventListener('hashchange', fn);
    }

    return () => {
      if(process.browser){
        window.removeEventListener('hashchange', fn);
      }
    }
  }  
);

export default hash;

then you can do

import hash from "hash";
<a href="#something" class:current={"something" === $hash}>

@ramiroaisen
Copy link

ramiroaisen commented Sep 4, 2019

Here is a better script handling mousewheel click and click with ctrlKey (opens in a new tab), contextmenu and focus for keyboard navigation

I realised now that the strange behavior is because sapper puts a <base href="/"> tag into the head but removing the tag breaks the site so for now we can just do as below

const key = 'data-original-hash-href';

const makeUrl = (hash) => {
	const current = document.location.href;
	return current.split("#")[0] + hash;
}

const findA = (el) => {
	do{
		if(el.tagName.toLowerCase() === "a" && (el.hasAttribute(key) || el.getAttribute("href")[0] === "#")){
			return el;
		}
	} while(el = el.parentElement);
}

const changeHref = (event) => {
	const a = findA(event.target);
	if (a) {
		let href;
		if(a.hasAttribute(key)){
			href = a.getAttribute(key);
		} else {
			href = a.getAttribute('href');
			a.setAttribute(key, href)
			a.href = makeUrl(href); 
		}

		if(event.button === 0 && !event.ctrlKey){
			event.preventDefault();
			document.location.hash = href;
		}
	}
}

document.addEventListener('click', changeHref, true);
document.addEventListener('contextmenu', changeHref, true);
document.addEventListener('focus', changeHref, true);
document.addEventListener('auxclick', changeHref, true);

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

4 participants