0

I am building an application with React Native and I am trying to implement payment system with paystack. I actually would like to use webview to display the pagstack payment gateway.

Everything is working well until I tested the cancel button which wasn't working. I had to use injectjavascript method to listen to click event and pragmatically navigate the user to the declined screen. The problem now that this code uses javascript setTimeout. This is because paystack firsts mount a loading state. So, setTimeOut was used to delay the script for 3 seconds. I don't like this. What if the loading takes way longer than that? I have tried window.load and some other recommendations but it is not working. The script runs immediately the paystack loading state is mounted. At this point, the document elements such as the span is yet to load. Here is the code:

    const handleCancleScript = `

  setTimeout(function() {
    const buttons = document.getElementsByClassName("text")

    for(let button of buttons){
    
       const resp = button.textContent
       if(resp == "Cancel Payment"){
        button.addEventListener("click", function() {  
            var response = {event:'cancelled'};
            window.ReactNativeWebView.postMessage(JSON.stringify(response))
            });
        
       }
       
    }
  
}, 3000);
  true; 
`

The webview:

 <WebView 
    javaScriptEnabled={true}
    source={{ uri: paymentData }}
    style={{ marginTop: 50 }}
    onNavigationStateChange={ handleNav }
    cacheEnabled={false}
    cacheMode={'LOAD_NO_CACHE'}
    javaScriptEnabledAndroid={true}
    ref={webView}


    onMessage={(e) => {
    messageReceived(e?.nativeEvent?.data);
   }}

  />

Is there really a way I can prevent this script from running until the paystack loading state has completed? I realized that paystack is using loading state when I inspected the html elements on browser.

1 Answers1

0

Which recommendations did you use? Did you try the props from React Native WebView? There are some interesting props like onLoad, onLoadEnd... React Native WebView API Reference . Maybe it could works in your case.

export function App() {
  const [isLoading, setIsLoading] = React.useState(false)
  let uri = "https://reactnative.dev/docs/environment-setup"

  function showSpinner() {
    setIsLoading(true)
  }

  function hideSpinner() {
    setIsLoading(false)
  }

  function onLoadStart() {
    showSpinner()
  }

  function onLoad() {
    hideSpinner()
  }

  function onLoadEnd() {
    hideSpinner()
  }

  return (
    <View style={styles.container}>
      {isLoading && (
        <View style={styles.loadingContainer}>
          <ActivityIndicator size="large" color="#0000ff" />
        </View>
      )}

      <WebView
        style={styles.webView}
        source={{ uri }}
        onLoadStart={onLoadStart}
        onLoad={onLoad}
        onLoadEnd={onLoadEnd}
      />
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  loadingContainer: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "white",
    zIndex: 1
  },
  webView: { flex: 1 }
})

If it doesn't work, maybe instead of using setTimeout with injectedJavaScript props, you could do that:

export const sleep = milliseconds => {
  return new Promise(resolve => setTimeout(resolve, milliseconds))
}

export function App() {
  const [isLoading, setIsLoading] = React.useState(false)
  let uri = "https://reactnative.dev/docs/environment-setup"

  const loadWebView = async () => {
    await sleep(3000)
    setIsLoading(false)
  }

  React.useEffect(() => {
    setIsLoading(true)
    loadWebView()
  }, [])

  return (
    <View style={styles.container}>
      {isLoading && (
        <View style={styles.loadingContainer}>
          <ActivityIndicator size="large" color="#0000ff" />
        </View>
      )}

      <WebView style={styles.webView} source={{ uri }} />
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  loadingContainer: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "white",
    zIndex: 1
  },
  webView: { flex: 1 }
})

This is the result:

enter image description here

Boopy
  • 309
  • 3
  • 5
  • Thank you for your detailed code. The first option wont work because the site is detected to have loaded by webview immediately the state of that site is set at isLoading. At this point, the script that I wrote in my app would fire and the script needed to detect a button which hasnt loaded yet because it ran when the site was onLoading sate. So, the first option wont work because when webView props detects the site to have loaded, it is actually still in loading state. The second option, could you explain better, please. There is a setTimeOut there. Is it not similar to what I wrote? – kingsJayson Feb 21 '23 at 17:47
  • It's similar, but it was just a suggestion to set a setTimeout to have more flexibility from the React part – Boopy Feb 21 '23 at 18:32
  • If the paystack page spends longer time to load change from loading state to loading false, it still doesn't solve my problem. The only way to fix this if there is a way to detect when the loading state changes from loading true, to loading false. Is this possible? Is there a way to wait until all external js files on the page loads or all the css files are loaded. I think the site was created with reactjs – kingsJayson Feb 21 '23 at 18:59
  • Did you know document.readyState? It could help you to know if the page is ready. https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState – Boopy Feb 21 '23 at 19:10
  • I checked that, it didnt work at all. I think the problem is that there is no way to track react state. The state is purely reactjs state in the paystack website. It simply hides the html file when loading is true while webview has detected that the page has loaded already completely. – kingsJayson Feb 21 '23 at 21:13