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

MIGRATED: iOS WebView doesn't support hash url change. The onNavigationStateChange listener will not be called when url hash changes. (#20447) #24

Closed
jamonholmgren opened this issue Sep 10, 2018 · 28 comments · Fixed by #2929
Labels
Help wanted Extra attention is needed migrated Migrated from https://github.com/facebook/react-native/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+ Stale

Comments

@jamonholmgren
Copy link
Member

Migrated from facebook/react-native#20447.

@ll929 says:


Environment
React Native Environment Info:
System:
OS: macOS High Sierra 10.13.3
CPU: x64 Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
Memory: 109.62 MB / 8.00 GB
Shell: 5.3 - /bin/zsh
Binaries:
Node: 9.11.1 - ~/.nvm/versions/node/v9.11.1/bin/node
Yarn: 1.7.0 - /usr/local/bin/yarn
npm: 5.6.0 - ~/.nvm/versions/node/v9.11.1/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
SDKs:
iOS SDK:
Platforms: iOS 11.2, macOS 10.13, tvOS 11.2, watchOS 4.2
Android SDK:
Build Tools: 23.0.1, 23.0.2, 23.0.3, 24.0.1, 25.0.1, 25.0.2, 25.0.3, 26.0.1, 26.0.2, 26.0.3, 27.0.0, 27.0.3
API Levels: 23, 24, 25, 26, 27
IDEs:
Android Studio: 3.1 AI-173.4819257
Xcode: 9.2/9C40b - /usr/bin/xcodebuild
npmPackages:
react: 16.4.1 => 16.4.1
react-native: 0.56.0 => 0.56.0

Description
Describe your issue in detail. Include screenshots if needed. If this is a regression, let us know.

Reproducible Demo
Let us know how to reproduce the issue. Include a code sample, share a project, or share an app that reproduces the issue using https://snack.expo.io/. Please follow the guidelines for providing a MCVE: https://stackoverflow.com/help/mcve

Expected Behavior
When url hash change, the webivew onNavigationStateChange listener will be invoked, and the canGoBack properties is true

Actual Behavior
When url hash changes, the webivew onNavigationStateChange hasn't been invoked

@jamonholmgren jamonholmgren added the migrated Migrated from https://github.com/facebook/react-native/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+ label Sep 10, 2018
ankur-sardar added a commit to lunascape/react-native-webview that referenced this issue Oct 25, 2018
make compatible with phoebe-mobile in RN version 0.57
@KoenLav
Copy link

KoenLav commented Oct 27, 2018

Should it change?

It should definitely be consistent between Android and iOS, but should it change when the URL hash changes?

The URL hash typically refers to some portion of the document; clicking an anchor link will fast forward through the document to that portion.

But is the behavior really any different from scrolling? Scrolling also moves the viewport to a different portion of the document.

I think if you look at most Router implementations, currently, they will actually change the URL (not just the hash) when any significant change in the UI occurs.

Then again I could imagine multi-step forms which use the hash to indicate at which portion of the form a user resides; should we support that?

Does onNavigationStateChange trigger any other functionality? Such as calling injectedJavascript?

@Titozzz
Copy link
Collaborator

Titozzz commented Oct 30, 2018

I think it would be useful to give more info to the users? I have use cases where I want to monitor when the url changes and I have to use postMessage inside react-router to notify react-native of the change (to update navigation title for example)

@xupengkun
Copy link

I think there are a lot of single-page applications (via hashes) that change, I think it's useful to be able to listen for hash changes.

@Titozzz
Copy link
Collaborator

Titozzz commented Nov 8, 2018

@xupengkun my use case, exactly

@R10burning
Copy link

so have u fixed this hash url change state guys?

@Titozzz
Copy link
Collaborator

Titozzz commented Nov 15, 2018

Not yet, feel free to send a PR

@liweijieok
Copy link

I has the same requirement too, I have a WebView which is a single-page application. And onNavigationStateChange only work in android, iOS has not reaction. So in the iOS device , the user browse the web page and click the left-top button, it will close the whole webview not that page.
if onNavigationStateChange work , i can be control the webview close or back to last page.
This is my problem, thanks.

@BANG88
Copy link

BANG88 commented Feb 18, 2019

Its very important for web apps please add this feature 😭

Please.

@NestedLooper
Copy link

Here is a solution that seems to work for me on iOS:

Inject JS onto the page that sets the initial url and then continuously checks the current url against the initial one. If the user has navigated to a new page, the url will be different. When that happens you can send a postMessage back to your React Native code to handle the change and then set the current url to be checked again.

Example:

// initial url on page load
let initialUrl = window.location.href;

// check for changes in url
let checkUrlChange = () => {
  //get current url
  const currentUrl = window.location.href;
  //if it has changed send a postmessage to rn and set the new url as the initial to check against
  if(currentUrl !== initialUrl){
    window.ReactNativeWebView.postMessage('{ \"locationUpdated\": \"' + currentUrl + '\" }');
    initialUrl = currentUrl;
  }
}

//check for changes every 2 seconds
setInterval(checkUrlChange, 2000)

As an injectable template string, this would look like:

let injectedJs = `
let initialUrl = window.location.href;
let checkUrlChange = () => {
  const currentUrl = window.location.href;
  if(currentUrl !== initialUrl){
    window.ReactNativeWebView.postMessage('{ \"locationUpdated\": \"' + currentUrl + '\" }');
    initialUrl = currentUrl;
  }
}
setInterval(checkUrlChange, 2000);`

Now, in your onMessage function you can listen for this object being sent back and then handle it however you need to.

onWebMessage = e => {
  const data = e.nativeEvent.data;
  if (data && data.includes('locationUpdated')) {
    let newLoc = JSON.parse(data);
    let theUrl = newLoc.locationUpdated;
    //handle the new URL or change anything needed on URL change			
  }
}

Some notes:

  • I am passing back a stringified JSON object because I listen for several different changes. You can just as easily pass back a string and not have to parse it.
  • This method can be used to check for things other than just a URL change. For example, you could set the height of the page to a variable and check for a change in that - in case something was dynamically added due to interactions on the page, but didn't cause a URL change.

@janicduplessis
Copy link

janicduplessis commented Apr 17, 2019

I was able to get this working pretty cleanly by monkey patching the history api with injectedJavaScript.

<Webview
  injectedJavaScript={`
    (function() {
      function wrap(fn) {
        return function wrapper() {
          var res = fn.apply(this, arguments);
          window.ReactNativeWebView.postMessage('navigationStateChange');
          return res;
        }
      }

      history.pushState = wrap(history.pushState);
      history.replaceState = wrap(history.replaceState);
      window.addEventListener('popstate', function() {
        window.ReactNativeWebView.postMessage('navigationStateChange');
      });
    })();

    true;
  `}
  onMessage={({ nativeEvent: state }) => {
    if (state.data === 'navigationStateChange') {
      // Navigation state updated, can check state.canGoBack, etc.
    }
  }}
/>;

Maybe this should be built in?

@adamrainsby
Copy link

@janicduplessis Am I missing something? It doesn't look like you can check state.canGoBack

@janicduplessis
Copy link

@adamrainsby state is ({ nativeEvent: state }). Are you on iOS, I did not test this on Android yet so it might be why.

@Titozzz
Copy link
Collaborator

Titozzz commented Apr 20, 2019

Interesting @janicduplessis! We might want to add this to the docs

@jamonholmgren
Copy link
Member Author

I added it to the docs ^^

@atlatosBast
Copy link

@janicduplessis workaround works perfectly on iOS, but unfortunately not on Android, where the nativeEvent only has the "data" property like stated in the docs. It only has all the other event properties on iOS.

@mcrowe
Copy link

mcrowe commented May 10, 2019

This issue seems to also occur whenever pushState is used to navigate, not just when the anchor changes.

@xcxooxl
Copy link

xcxooxl commented May 20, 2019

Waiting for a fix on android =\ Our website is made in Vue (SPA)
it uses pushState to navigate but there's no state property on Android.

@janicduplessis
Copy link

Is the workaround also needed on Android? I haven't tested it yet.

@manfwh
Copy link

manfwh commented May 30, 2019

@Titozzz

state.canGoBack == undefined

@swansontec
Copy link
Contributor

I actually submitted a pull request, #641, to fix this in the WebView itself. The pull request doesn't interfere with the onMessage mechanism, since I was able to hook the event in at a lower level.

I closed the pull request because it didn't look like it would work, but perhaps the window.addEventListener('popstate', ...) bit is the ticket I need to success.

I am dedicated to getting a proper in-tree solution to this problem. I am also willing to consider other API's as well. It seems like people would also like access to the state JSON from window.history.pushState, besides fixing the original issue?

@KingAmo
Copy link

KingAmo commented Jul 13, 2019

@janicduplessis it works in iOS, but not works in Android... need help please :(
in Android, onMessage receive a message like below
image
no addition info that i need like canGoBack or title

@github-actions
Copy link

Hello 👋, this issue has been opened for more than 2 months with no activity on it. If the issue is still here, please keep in mind that we need community support and help to fix it! Just comment something like still searching for solutions and if you found one, please open a pull request! You have 7 days until this gets closed automatically

@github-actions github-actions bot added the Stale label Sep 15, 2019
@swansontec
Copy link
Contributor

If the issue is still here, please keep in mind that we need community support and help to fix it!

Silly bot, there has been an open pull request on this issue for months! So, yes, the community is trying to help fix issues, but the maintainers don't seem interested in accepting help.

@KingAmo
Copy link

KingAmo commented Sep 17, 2019

@swansontec it is really sad :(

@timraybould
Copy link

Here is a solution for grabbing the current URL that works on iOS and Android. It's an adaptation of the code from @janicduplessis above. Again, focused on passing back current URL only, not the other bits normally received in onNavigationStateChange.

<Webview
  injectedJavaScript={`
    (function() {
      function wrap(fn) {
        return function wrapper() {
          var res = fn.apply(this, arguments);
          window.ReactNativeWebView.postMessage(window.location.href);
          return res;
        }
      }
      history.pushState = wrap(history.pushState);
      history.replaceState = wrap(history.replaceState);
      window.addEventListener('popstate', function() {
        window.ReactNativeWebView.postMessage(window.location.href);
      });
    })();
    true;
  `}
  onMessage={event => {
    console.log(event.nativeEvent.data)
  }}
/>

As the docs for onMessage say, whatever string you pass into postMessage becomes available via onMessage at whateveryoucallthereturnedobject.nativeEvent.data

@eyadabdalla
Copy link

@timraybould I just tried what you have suggested, but seems to only detect the first time the navigation page changes, but not for times after that. Any suggestions?

@bareynol
Copy link

bareynol commented Apr 2, 2020

@timraybould this works perfectly for me on Android!

Thank you! 👏

andrepimenta added a commit to MetaMask/react-native-webview that referenced this issue Jun 12, 2020
@zhangwen9229
Copy link

zhangwen9229 commented Jan 18, 2022

Here is a solution for grabbing the current URL that works on iOS and Android. It's an adaptation of the code from @janicduplessis above. Again, focused on passing back current URL only, not the other bits normally received in onNavigationStateChange.

<Webview
  injectedJavaScript={`
    (function() {
      function wrap(fn) {
        return function wrapper() {
          var res = fn.apply(this, arguments);
          window.ReactNativeWebView.postMessage(window.location.href);
          return res;
        }
      }
      history.pushState = wrap(history.pushState);
      history.replaceState = wrap(history.replaceState);
      window.addEventListener('popstate', function() {
        window.ReactNativeWebView.postMessage(window.location.href);
      });
    })();
    true;
  `}
  onMessage={event => {
    console.log(event.nativeEvent.data)
  }}
/>

As the docs for onMessage say, whatever string you pass into postMessage becomes available via onMessage at whateveryoucallthereturnedobject.nativeEvent.data

Very perfect. And we can post window.history.length.

pushState:
window.postMessage(JSON.stringify({type:'navigationStateChange',params:{hisLen: history.length}}));
popState:
window.postMessage(JSON.stringify({type:'navigationStateChange',params:{hisLen: history.length-1}}));

Webview onMessage

let canGoBack = false;
......
canGoBack = event.nativeEvent.data.params.hisLen != 1;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Help wanted Extra attention is needed migrated Migrated from https://github.com/facebook/react-native/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+ Stale
Projects
None yet