saurabhnemade / react-twitter-embed

Simplest way to add twitter widgets to your react project.
https://saurabhnemade.github.io/react-twitter-embed/
367 stars 66 forks source link

String ref warning when using React in Strict Mode #71

Open christinegaribian opened 4 years ago

christinegaribian commented 4 years ago

When using TwitterTweetEmbed in React.StrictMode, I get this error in the console:

index.js:1 Warning: A string ref, "embedContainer", has been found within a strict mode tree. 
String refs are a source of potential bugs and should be avoided. 
We recommend using useRef() or createRef() instead. 
Learn more about using refs safely here: https://fb.me/react-strict-mode-string-ref
    in TwitterTweetEmbed
kacper-zelichowski commented 3 years ago

Seems like a quick fix, any ideas if someone's on it? :)

NickCarducci commented 3 years ago

This kind of malfeasance breaks copyright, just use createRef() in constructor. Still don't know how to do the github pushes

mrawal3112 commented 3 years ago

Did anyone got the solution of this issue.

NickCarducci commented 3 years ago

@mrawal3112 EDIT: TwitterTweetEmbed.js: here is a file essentially copied from this library, except to use this.ref = React.createRef(), with protection from script mounting before DOM mounting on the node in case either window.twttr or this.tw.current fails mount (Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'), I have no idea how when it is in componentDidMount, but with a lot of tweets with videos... I best not guess.

import React, { Component } from "react";
import PropTypes from "prop-types";
import ExecutionEnvironment from "exenv";
import script from "scriptjs";

class Forward extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    return <div ref={this.props.fwdtwe} />;
  }
}
const Forwardd = React.forwardRef((props, ref) => (
  <Forward fwdtwe={ref} {...props} />
));
export default class TwitterTweetEmbed extends Component {
  static propTypes = {
    /**
     * Tweet id that needs to be shown
     */
    tweetId: PropTypes.string.isRequired,
    /**
     * Additional options to pass to twitter widget plugin
     */
    options: PropTypes.object,
    /**
     * Placeholder while tweet is loading
     */
    placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
    /**
     * Function to execute after load, return html element
     */
    onLoad: PropTypes.func
  };

  constructor(props) {
    super(props);
    this.state = {
      giveup: 0,
      isLoading: true
    };
    this.tw = React.createRef();
    this.fwdtwe = React.createRef();
  }

  renderWidget() {
    const { onLoad } = this.props;
    if (!window.twttr) {
      console.error(
        "Failure to load window.twttr in TwitterTweetEmbed, aborting load."
      );
      return;
    }
    if (!this.tw.current) {
      console.error(
        "Failure to load this.tw.current in TwitterTweetEmbed, aborting load.",
        this.tw.current
      );
      return;
    }
    /*let onClick = "ontouchstart" in window ? "touchstart" : "onclick";
    if (!this.tw.current["onClick"]) {
      console.error(
        "Failure to load this.tw.current[onClick] in TwitterTweetEmbed, aborting load.",
        this.tw.current["onClick"]
      );
      return;
    }*/
    if (!this.isMountCanceled && this.props.tweetId) {
      //this.tw.current.innerHTML = React.createElement("div", { ref: this.twe });
      //ReactDOM.render(reactElementUl, document.getElementById('app'));
      /*ReactDOM.render(
        forwardd(), //React.createElement("div", { ref: this.twe }),
        this.tw.current
      );*/
      this.tw.current.innerHTML = <Forwardd fwdtwe={this.fwdtwe} />;
      window.twttr.widgets
        .createTweet(
          this.props.tweetId,
          this.fwdtwe.current,
          this.props.options
        )
        .then((element) => {
          this.setState({
            isLoading: false,
            style: { height: "min-content", ...this.props.style }
          });
          if (onLoad) {
            onLoad(element);
          }
        })
        .catch((err) => {
          if (this.state.giveup === 2) return null;
          this.setState({ giveup: this.state.giveup + 1 }, () => {
            clearTimeout(this.timer);
            this.timer = setTimeout(() => {
              this.renderWidget();
            }, 1000); //in case either window.twttr or this.tw.current fails mount
          });
        });
    }
  }

  componentDidMount() {
    if (ExecutionEnvironment.canUseDOM) {
      //let script = require("scriptjs");
      script("https://platform.twitter.com/widgets.js", "twitter-embed", () => {
        this.renderWidget();
      });
    }
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
    this.isMountCanceled = true;
  }

  render() {
    return <div ref={this.tw} style={this.state.style} />;
  }
}

TwitterTweetEmbed.js: here is a file essentially copied from this library, except to use this.ref = React.createRef()

import React, { Component } from "react";
import PropTypes from "prop-types";
import ExecutionEnvironment from "exenv";

export default class TwitterTweetEmbed extends Component {
  static propTypes = {
    /**
     * Tweet id that needs to be shown
     */
    tweetId: PropTypes.string.isRequired,
    /**
     * Additional options to pass to twitter widget plugin
     */
    options: PropTypes.object,
    /**
     * Function to execute after load, return html element
     */
    onLoad: PropTypes.func
  };

  constructor(props) {
    super(props);
    this.state = {
      isLoading: true
    };
    this.tw = React.createRef();
  }

  renderWidget() {
    const { onLoad } = this.props;
    if (!window.twttr) {
      console.error(
        "Failure to load window.twttr in TwitterTweetEmbed, aborting load."
      );
      return;
    }
    if (!this.isMountCanceled) {
      window.twttr.widgets
        .createTweet(this.props.tweetId, this.tw.current, this.props.options)
        .then((element) => {
          this.setState({
            isLoading: false
          });
          if (onLoad) {
            onLoad(element);
          }
        });
    }
  }

  componentDidMount() {
    if (ExecutionEnvironment.canUseDOM) {
      let script = require("scriptjs");
      script("https://platform.twitter.com/widgets.js", "twitter-embed", () => {
        this.renderWidget();
      });
    }
  }

  componentWillUnmount() {
    this.isMountCanceled = true;
  }

  render() {
    return (
        <div ref={this.tw} />
    );
  }
}

with widget declarations for how to read tweetId, for isHashtag ("createHashtagButton") or isProfile ("createFollowButton"), lest is "createTweet"


renderWidget() {
  const { onLoad } = this.props;
  if (!window.twttr) {
    console.error(
      "Failure to load window.twttr in TwitterTweetEmbed, aborting load."
    );
    return;
  }
  if (!this.isMountCanceled) {
    let options = Object.assign({}, this.props.options);
    var widget = null;
    var input = null;
    if (this.props.isHashtag) {
      widget = "createHashtagButton";
      input = this.props.tweetId;
    } else if (this.props.isProfile) {
      //console.log("twitter profile " + this.props.tweetId);
      options = Object.assign({}, options, {
        theme: this.props.theme,
        linkColor: this.props.linkColor,
        borderColor: this.props.borderColor,
        lang: this.props.lang
      });
      widget = "createFollowButton";
      input = this.props.tweetId;
      /*widget = "createTimeline";
      input = {
        sourceType: "profile",
        screenName: this.props.tweetId,
        userId: this.props.userId,
        ownerScreenName: this.props.ownerScreenName,
        slug: this.props.slug,
        id: this.props.id || this.props.widgetId,
        url: this.props.url
      };*/
    } else {
      widget = "createTweet";
      input = this.props.tweetId;
    }
    //this.refs.embedContainer
    window.twttr.widgets[widget](input, this.tw.current, options).then(
      (element) => {
        this.setState({
          isLoading: false
        });
        if (onLoad) {
          onLoad(element);
        }
      }
    );
  }
}
chris-erickson commented 2 years ago

Works great for React 17, but in 18 the tweet just never loads. Is that an easy adjustment as well? No errors so it seems like the tweet component just isn't being attached to the empty placeholder div in the DOM at all.