Is there any way to have a permanent element between transitions in React Navigation with Native Stack?

Viewed 93

I am trying to replicate the Turbo Native behavior. I have a WebView, and every time a link is visited I want to make a transition and add it to the navigation stack. The problem is that when I do that with React Navigation, it re-renders from the beginning the WebView again for each screen. I would like to reuse the same WebView instance between screens when doing push and pop so I don't have to reload all the assets and javascript over and over again. Is there any way to do this? Thanks!

This is a sample code:

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Web View"
        onPress={() => navigation.navigate('WebView', {
          url: "https://someurl/"
        })}
      />
    </View>
  );
}

function WebViewScreen({ route, navigation }) {
  const webViewRef = useRef(null);
  const [URL, setURL] = useState("");
  const [title, setTitle] = useState("");
  useFocusEffect(
    useCallback(() => {
      // Do something when the screen is focused
      setURL(route.params.url);
      setTitle(route.params.title);

      return () => {
        // Do something when the screen is unfocused
        // Useful for cleanup functions
      };
    }, [])
  );
  useEffect(() => {
    if (URL && URL != route.params.url) {
      navigation.push(route.name, {
        url: URL,
        title: title,
      });
    }
  }, [URL]);
  useEffect(() => {
    if (title && !route.params.title) {
      navigation.setOptions({ title: title })
    } else {
      navigation.setOptions({ title: route.params.title })
    }
  }, [title]);
  const handleNavigation = (syntheticEvent) => {
    const { url, title } = syntheticEvent.nativeEvent;
    if (!url) return;
    setURL(url);
    setTitle(title);
  };
  const runFirst = `
    setTimeout(function() { window.ReactNativeWebView.postMessage("Hello!") }, 2000);
    true; // note: this is required, or you'll sometimes get silent failures
  `;
  return (
    <Freeze freeze={!webViewRef}>
      <WebView
        ref={webViewRef}
        style={styles.container}
        source={{ uri: route.params.url }}
        originWhitelist={['*']}
        allowingReadAccessToURL={'*'}
        allowFileAccess
        allowFileAccessFromFileURLs
        allowUniversalAccessFromFileURLs
        javaScriptEnabled
        javaScriptCanOpenWindowsAutomatically
        domStorageEnabled
        mixedContentMode={'always'}
        onLoadEnd={handleNavigation}
        injectedJavaScript={runFirst}
        onMessage={(event) => {
          alert(event.nativeEvent.data);
        }}
      />
    </Freeze>
  );
}

const HomeStack = createNativeStackNavigator();

function HomeStackScreen() {
  return (
    <HomeStack.Navigator>
      <HomeStack.Screen name="Inicio" component={HomeScreen} />
      <HomeStack.Screen
        name="WebView"
        component={WebViewScreen}
        initialParams={{
          url: "https://someurl/"
        }}
      />
    </HomeStack.Navigator>
  );
}

The injectedJavaScript property injects javascript on the first load of the webview. Here, on each request it is executing that code, and it should only do so once. This is a way to test it.

1 Answers

I think we can use the history of the web through message communication.

  • Example
const HTML = `
<!DOCTYPE html>
<html>
  <head>
      <title>Local File</title>
      <script>
      function onPress(){
      var data = {
        "type": "back",
        "params": {}
      }
      window.ReactNativeWebView.postMessage(JSON.stringify(data));
    }
    </script>
  </head>
  <body>
  <h1>TBAF Test Page</h1>
    <form> 
      <input id="but1" class="input" type="button" value="Checkout" onclick="onPress()" />
    </form>
    <p id="demo"></p>
  </body>
</html>`;

const webviewRef = useRef(null)
  return (
    <WebView
      ref={webviewRef}
      source={{ html: HTML }}
      style={{ marginTop: 20 }}
      onMessage={(event) => {
        const {type, params} = event.nativeEvent.data
        switch(type) {
          case "back":
          webviewRef.current.goBack()
          break;
        }
      }}
    />
  );

Related