Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow changing forceRefresh #614

Closed
wants to merge 2 commits into from

Conversation

stipsan
Copy link

@stipsan stipsan commented Aug 4, 2018

TL;DR https://codesandbox.io/s/n77x71v6x4

This has been discussed in #477. The suggestion then were to use window.location.reload when the user clicks on a link. Having tested this solution in production I feel this topic is worth a revisit 🤔

The codesandbox demonstrates the difference between using window.location.reload and having the option to set forceRefresh by calling history.setForceRefresh itself in a typical react-router setup.

Since the call to window.location.reload is async the page usually unloads after rendering the same route that is being navigated to, making it look like something went wrong to the end user:

kapture 2018-08-04 at 19 56 26

Compare it to forceRefresh:

kapture 2018-08-04 at 19 58 22

I tried my best to find alternative solutions but doing something the equivalent of:

import {Router} from 'react-router-dom';

const history = createBrowserHistory();
const forceRefreshHistory = createBrowserHistory({forceRefresh: true});

...
return <Router history={shouldForceRefresh ? forceRefreshHistory : history}>

Turned out to be surprisingly difficult. As was trying to wrap every <Link /> and <Redirect /> with custom logic that overrides events and essentially reimplement what forceRefresh in history does to achieve the same result. But as it turns out there's more logic than react components that might trigger a navigation events so this too is fragile (and a lot of code compared to just rendering a component somewhere wrapped in withRouter that calls history.setForceRefresh(true) and that's it).

What do you think?

@kevinlaw91
Copy link

kevinlaw91 commented May 28, 2019

+1 This is also useful for react-router for site force fresh.
e.g. if react app is updated, we can tell history to use force refresh for next coming navigation
Maybe you can update the PR title for broader use case

@mjackson
Copy link
Member

mjackson commented Sep 9, 2019

Thank you for the detailed demo, @stipsan! Very nice. I don't think I realized that window.location.reload() would wait until the new route was rendered before refreshing the page.

Still, I wonder if setForceRefresh is the right API. Changing the configuration of the history object itself is going to change the behavior of all links on the page. But it feels like there could be links on the same page, some of which navigate seamlessly and others which cause a reload.

Maybe it'd be better to expose this behavior in push, like push(url, state, forceRefresh = false). At a higher level, we could perhaps provide a <Refresh path="..."> component or similar in the router that would set this up for you.

wdyt?

@mjackson mjackson changed the title Add history.setForceRefresh() to allow smooth app refresh opportunities Allow changing forceRefresh Sep 9, 2019
@mjackson
Copy link
Member

mjackson commented Nov 8, 2019

Just following up here: this issue has been on my mind for a while now. I'm thinking we can maybe address it in v5 with a new reload arg to push/replace so you can opt-in to full page reloading when you actually initiate navigation instead of when you create your history object. Maybe something like:

history.push(`/gallery`, /* state */ null, /* reload */ true);

I have a TODO about it on the dev branch.

What do you think, @stipsan? Would that work?

@mjackson mjackson mentioned this pull request Nov 8, 2019
Merged
@kevinlaw91
Copy link

Hi @mjackson, can we have an additional option to switch to global force refresh mode too? This can be useful when server push a "new version available" signal to the web app, and the app will refresh automatically during next navigation.

jeresig added a commit to Khan/history that referenced this pull request Mar 25, 2020
Re-implementation of:
remix-run#614

Includes the built artifacts so that we can use it in from `package.json.

Issue: remix-run#614

Test plan:
It should now be possible to call `setForceRefresh` and dynamically change this setting!
jeresig added a commit to Khan/history that referenced this pull request Mar 25, 2020
Re-implementation of:
remix-run#614

Includes the built artifacts so that we can use it in from `package.json.

Issue: remix-run#614

Test plan:
It should now be possible to call `setForceRefresh` and dynamically change this setting!
jeresig added a commit to Khan/history that referenced this pull request Mar 25, 2020
Allow changing forceRefresh

Re-implementation of:
remix-run#614

Includes the built artifacts so that we can use it in from `package.json.

Issue: remix-run#614

Test plan:
It should now be possible to call `setForceRefresh` and dynamically change this setting!
@chaance chaance deleted the branch remix-run:master August 14, 2021 18:04
@chaance chaance closed this Aug 14, 2021
@chaance
Copy link
Contributor

chaance commented Aug 14, 2021

This was unintentionally closed because I deleted the master branch after renaming to main, very sorry about that! Feel free to open a new PR, but know I've bookmarked this for review either way.

@spikebrehm
Copy link

Hello folks! Was just looking into the ability to change the forceRefresh value and found this.

What we've done in the meantime is extend History like so:

import { Action, History, Location } from 'history';

export interface RefreshingHistory<T> extends History<T> {
  setForceRefresh(val: boolean): void;
}

export default function hardRefreshable<T>(
  h: History<T>
): RefreshingHistory<T> {
  let forceRefresh = false;
  h.listen((loc) => {
    if (forceRefresh) {
      window.location.href = h.createHref(loc);
    }
  });

  return {
    ...h,

    // We need to do a bit more work to proxy the non-method properties
    get length() {
      return h.length;
    },
    set length(l: number) {
      h.length = l;
    },
    get action() {
      return h.action;
    },
    set action(a: Action) {
      h.action = a;
    },
    get location() {
      return h.location;
    },
    set location(l: Location<T>) {
      h.location = l;
    },

    setForceRefresh(val: boolean) {
      forceRefresh = val;
    },
  };
}

@iamsaumya
Copy link

iamsaumya commented Oct 4, 2021

TL;DR - Are there any caveats of implementing this? Do we just have to make the changes in the component rendering Router?

Thanks for posting the workaround. I have used this particular snippet in the following way. However, every now and then I stumbled across inconsistencies of the path not being updated swiftly. I will have to click a few times to make it work. In my app, I've imported useHistory hook from react-router-dom and use it push, pop and goBack etc.

import * as React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';

import hardRefreshable, { RefreshingHistory } from 'utils/hardRefreshable';

function App() {
  const refreshableHistory: RefreshingHistory<{}> = hardRefreshable(
    createBrowserHistory<{}>()
  );

  React.useEffect(() => {
    const id = setTimeout(
      () => {
        console.log('Hard refresh on');
        refreshableHistory.setForceRefresh(true);
      },
      1000 * 20 // 20 secs for testing purpose
    );
    return () => clearTimeout(id);
  }, [refreshableHistory]);

  return (
    <Router history={refreshableHistory}>
      <RoutesComponent />
    </Router>
  );
}

export default App;

Sometimes I get this warning in the console Warning: You cannot change <Router history>.


EDIT: I seemed to have solved the issue by memoizing refreshableHistory using useMemo.

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

Successfully merging this pull request may close these issues.

6 participants