7

In the below code, I expected the webView content to not change when the clicks are increased, but every time it loads, a new timestamp is displayed.

const webView = (
  <WebView
    source={{
      uri:
        'data:text/html,<html><script>document.write("<h1 style=\\"font-size:64px\\">"+Date.now()+"<h1>");</script>',
    }}
  />
);


export default class App extends React.Component {
  state = {
    clicks: 0,
  };

  onClick = () => {
    this.setState({ clicks: this.state.clicks + 1 });
  };

  render() {
    return (
      <View>
        <Text onPress={this.onClick}>
          Click Me: {this.state.clicks}
        </Text>

        {this.state.clicks % 2 === 0 ? webView : null}
        {this.state.clicks % 2 === 1 ? webView : null}
      </View>
    );
  }
}

Link to expo snack to check it on a device.

So far, I've read about reparenting in React on issues here, implementing using Portals, and also saw an issue on supporting reparenting in react native with no resolution.

So, how to reuse a component instance in across multiple screens with out creating a new instance of it in every screen?

Was hoping reparenting would be the answer, but can't find any implementations, so if reparenting is the answer to this, how to implement it myself?

Prasanth
  • 5,230
  • 2
  • 29
  • 61
  • 1
    Did you find some solution? – Maycon Mesquita May 20 '20 at 15:14
  • Nope.. The one answer posted here gives me hope. I believe if the view is not detached from the node tree in any render call, it should be possible to move it around in the node tree with out causing a recreation of the webview. Something like a "dock" here when not used should work. I am planning to work on this over the weekend. Hopefully will have something reliable to work with. – Prasanth May 20 '20 at 16:26
  • I found a solution, please see my answer asap :) – Maycon Mesquita May 21 '20 at 02:25
  • 1
    will check this out.. thanks! don't worry about the bounty expiring, i'll surely award it if it works out ;) – Prasanth May 21 '20 at 07:22

3 Answers3

3

The problem here is that on every state change your component will re-render webView object and will show the current date. I suggest that you change webView to a component and add a static key when you call WebViewComp to prevent unmount/mount on every state change.

const WebViewComp = () => ( //Change declaration to function component instead of constant object
  <WebView
    source={{
      uri:
        'data:text/html,<html><script>document.write("<h1 style=\\"font-size:64px\\">"+Date.now()+"<h1>");</script>',
    }}
  />
);

export default class App extends React.Component {
  state = {
    clicks: 0,
  };

  onClick = () => {
    this.setState({ clicks: this.state.clicks + 1 });
  };

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.paragraph} onPress={this.onClick}>
          Click Me: {this.state.clicks}
        </Text>

        {this.state.clicks % 2 === 0 ? <WebViewComp key="child" /> : null}
        {this.state.clicks % 2 === 1 ? <WebViewComp key="child" /> : null}
      </View>
    );
  }
}
Mahdi N
  • 2,128
  • 3
  • 17
  • 38
  • 1
    It's not exactly like this. Element (not a component) is rendered only once when the app starts. After that, the result of that render is being inserted in different places in the app and therefore react-native will create different *native* WebViews for it. In your example creating WebViewComp is not necessary, you could achieve the same effect by just adding key to the WebView in the initial example. It still doesn't solve the problem, as even though it doesn't reset time anymore, it is only as long as WebView remains within one parent – Max May 17 '20 at 21:08
  • ... try moving WebView between different parents and it will always change time regardless of keys. Because currently it can't be done in react-native – Max May 17 '20 at 21:09
  • yes, this seems to work, but not quite. To give an example of when it doesn't work, try adding `% 3` instead of 2 and notice that the webviewcomp gets unmounted since it's not part of the dom any more and then a new number appears. – Prasanth May 18 '20 at 07:17
  • 1
    Reparenting is possible using React Native + UIManager. If you get the View's index, you can permute child at index 0 and 1 of parent view 6 => Example Code: UIManager.manageChildren(6, [0], [1], [], [], []); ...You only need to know the child index, from inside of the tree. I answered about a custom native lib that make it easy for you. – Maycon Mesquita May 21 '20 at 03:10
3

You definitely need to reparenting the view. I searched some libs that work as React Portals does.

We have two projects available:

I tested the second package (rn-native-portals) and this magically worked on Android:

How to install

  1. npm install mfrachet/rn-native-portals

  2. react-native link (unfortunately we can't auto-link this yet, but we can submit PR)

Implementation

Your target element needs to be inside <PortalOrigin>

import React from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { PortalOrigin } from 'rn-native-portals';

class Target extends React.Component {
  state = {
    moveView: false,
  }

  render() {
    return (
      <>
        <TouchableOpacity
          style={{ flex: 1 }}
          onPress={() => this.setState({ moveView: !this.state.moveView })}
        >
          <Text>Press Here</Text>
        </TouchableOpacity>

        <PortalOrigin destination={this.state.moveView ? 'destinationPortal' : null}>
          <View>
            <Text>This text will appear on destination magically...</Text>
          </View>
        </PortalOrigin>
      </>
    );
  }
}

export default Target;

On destination use this (don't forget set the same unique portal's name)

import React from "react";
import { PortalDestination } from "rn-native-portals";

class Destination extends React.Component {
  render() {
    return (
      <PortalDestination name="destinationPortal" />
    );
  }
}

export default Destination;

This project is amazing, but definitely need our community help to create a better documentation.

I have one project that need to use this feature, reparenting a video to the outside of screen. I'm seriously considering PR auto-link support to avoid compiling warnings.

More useful info about:

Maycon Mesquita
  • 4,470
  • 2
  • 21
  • 29
  • 1
    I am sorry I haven't yet gone through this and realised that the bounty has been auto awarded!! Did not expect it at all. And can say it's 100% unfair the way it happened should your answer work for me. I have flagged it hoping for a change. Should it not be address, I'll start another bounty and award it. Thanks. – Prasanth May 21 '20 at 15:52
  • 1
    This works. Just tested it myself. It is truly awesome! I am thinking next steps would be iOS support, auto linking, make it available with expo with out needing to eject. Hope we find assistance from the community to get this working. – Prasanth May 27 '20 at 20:05
  • 1
    @Prasanth Nice to see your message! Yes, this is awesome!! There is a commit (the latest) updating the xcode project. I didn't test iOS, but i think should work? Shouldn't? – Maycon Mesquita May 27 '20 at 21:32
0

Haven't tried the accepted answer's projects but, for React Native, @gorhom/portal works like a charm retaining context like a champ!

Adrian Bartholomew
  • 2,506
  • 6
  • 29
  • 37