The error that should have never been. In 2016, GitHub user Robert Roskam (raiderrobert) on the React Native repository reporting the error . In the two years since then, nothing has been done to resolve it within the internal React Native implementation of WebView. opened an issue “Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined” The React Native community has specifically to maintain it as a third party package and fix many of these ongoing issues. However, in order to implement these third party packages, you must be able to link React Native packages — . If you are able and willing to do this, your problem is solved. The installation instructions for the community edition of WebView are as simple as: forked WebView react-native link react-native-webview yarn add https://github.com/react-native-community/react-native-webviewreact-native link react-native-webview In order to , you must first . Note: react-native link ... yarn global add react-native Unfortunately, if you are unable or unwilling to do this, there has simply been no solution to this problem. For years! Users of Expo, for example, would have to eject their project and write their own native, non-JavaScript implementation of features. Expo will, theoretically, be using these community edition packages in future releases; but with a launch window only weeks away, my team and myself were not willing to wait. The Solution 💡 If you care more about getting this solved than how it works, this section is for you. right now Either or to add the to your project. npm install rn-webview --save yarn add rn-webview [rn-webview](https://www.npmjs.com/package/rn-webview) package Wherever you are using , simply replace it with . Then just use the new WebView component as you would the React Native internal implementation, including the use of the prop. The package is just a wrapper for the internal React Native implementation that intercepts messages through a different channel than the internal prop, but handles it with its own prop, giving the illusion that you are actually using the internal with expected results. import { WebView } from 'react-native' import WebView from 'rn-webview' onMessage rn-webview onMessage onMessage onMessage Caveats 🤕 The package works by directing traffic to instead. While React Native’s iOS implementation cannot handle correctly, it can handle navigation state changes. Because of this, the navigation state change event is the channel through which messages are transferred between the WebView and the native application. rn-webview window.postMessage history.pushState window.postMessage If manipulation of the history state is an important aspect of your application, this solution may not suit your needs. Feel free to to offer alternative solutions. Pull requests and issues are welcome! fork the project on GitHub The Implementation 🔨 Export 🚢 First and foremost, the prop of WebView is a particularly important one. Because of this, we don’t want the user to lose access to it. We start the package with a implementation, where is the class name used for this package. ref forwardRef WebViewPostMessage Render 🎨 The output of this component is going to be the React Native internal implementation of WebView, with a few tweaks. We aren’t going to give it the prop, because that is only used to give the parent access to the and is totally meaningless to the internal WebView. Most importantly, we aren’t going to give it the prop, because that is the source of all of our problems — it’s not supported by iOS! forwardedRef ref onMessage We have a custom navigation state change listener, because that is the channel through which we will be listening for messages. We have a custom ref handler, because we both 1) need access to it inside this component and 2) need to pass the ref back to the parent container via the forwardedRef prop. Ref 👋 When the internal WebView gives us its ref, we store it on the instance ( ) for use later. If the parent requested the ref as well, we forward it. this.ref = ref Inject 💉 window.postMessage Now, a custom implementation of needs to exist on any page in the WebView. Whenever the navigation state changes, if it has finished loading, we inject JavaScript into it to override what does. window.postMessage window.postMessage I defined and imported from a different file for readability. injectPostMessage It is an to make sure none of our variables conflict with the web page. immediately-invoked function expression The is what is pushed to history, since we won’t be using a state object for our event listener. EMPTY_STATE The function escapes apostrophes in a string so that we can place that string in apostrophes. Since the navigation state that we push is not real JavaScript and won’t be passed through any sort of JavaScript interpreter, this step is not exactly necessary. It just allows the state we push to more closely mimic real JavaScript. escape The variable checks to see if a function already exists. If so, we’ll want to execute it also during any calls. postMessage postMessage window.postMessage We define our own function. The first thing it does is executes the previous function, if it existed. window.postMessage window.postMessage Next, we push to the history state. We have no state object, so we use the aforementioned empty one. The title of the document is not changing, so we just use the current one. The location of the document is also not changing per se: we are merely appending a hash. That hash, which we’ll be listening for later, is . It looks like JavaScript, by design, but is not going to be evaluated by any real JavaScript interpreter. We just need a unique hash that won’t collide with real, in-document hashes. window.postMessage('the message') postMessage Listener 📬 Now that we have our own event emitter, we need to listen for it. This is the code that goes at the top of the method. window.postMessage handleNavigationStateChange We check if the new URL matches the hash we defined earlier. If it does, we’re going to so that the rest of the navigation state change event listener doesn’t fire. This is a message event, not a navigation state change (technicalities aside). postMessage return Each event will fire the navigation state change twice — once for and one, almost immediately after, for . We are only listening for the event, because it occurs first. The event is ignored, because it is just a duplicate. postMessage loading: true loading: false loading: true loading: false Only if the parent component passed an event handler, we call that handler with a mock event that contains the message. We unescape the message before passing it, because we escaped the apostrophes earlier. onMessage The unescape function is defined at the top of the document, because it is constant (does not depend on the instance) and does not need to be a method of the component. You may import it if you prefer to code split it. onNavigationStateChange 🕵 The above covers everything we need for intercepting and handling it with one’s own event listener. Our original problem is already solved — works with this WebView. However, since we have overwritten the internal listener, the parent is no longer receiving navigation state change events. window.postMessage onMessage onMessage onNavigationStateChange At the bottom of the event listener, add the following: handleNavigationStateChange If the parent has included an prop, call it, and give it this navigation state change event. onNavigationStateChange The empty return is simply personal preference — I don’t believe functions should conditionally return, even if it’s functionally equivalent to an implicit return. Conclusion 🔚 As a reminder, you can include the component just outlined by installing the from NPM. You may also . Issues and pull requests are welcome. [rn-webview](https://www.npmjs.com/package/rn-webview) package fork it on GitHub If you liked this article, feel free to give it a clap or two. It’s quick, it’s easy, and it’s free! If you have any questions or relevant commentary, please leave them in the comments below. To read more of my columns, you may follow me on and , or . LinkedIn Twitter check out my portfolio on CharlesStover.com
Share Your Thoughts