alexgarces / react-typeform-embed

React wrapper for Typeform Embed SDK
https://alexgarces.github.io/react-typeform-embed/
MIT License
124 stars 35 forks source link

No SSR support #5

Open goldylucks opened 6 years ago

goldylucks commented 6 years ago

Workaround: render component only after mounting

alexgarces commented 6 years ago

@goldylucks not sure if it is possible, does the typeform library allow it?

I'll try to do some research. Thanks!

goldylucks commented 6 years ago

I ditched typeform, doesn't work for embedding + power using. I developed my own platform, much faster and better ;)

Vadorequest commented 6 years ago

Same issue.

Another workaround would be to check if window is defined and do nothing if it's not.

      if (typeof window !== 'undefined') {

      }

Too bad because the lib looks promising :/

@goldylucks What platform did you build?

@alexgarces My use case was using your plugin with Next.js, which is a node SSR framework. https://github.com/zeit/next.js/

But so far, just loading import { ReactTypeformEmbed } from 'react-typeform-embed'; makes the app crash because it's expecting window

dvakatsiienko commented 6 years ago

@Vadorequest Hi! I'm facing the same situation with next.js. and so far getting this error

Uncaught TypeError: Cannot read property 'border-radius:0;display:block;height:2px;width:25px;position:absolute;right:6px;top:6px;' of undefined

Could you please share with your solution for embed typeform if you have any? Thank you!

Vadorequest commented 6 years ago

@dvakatsiienko My solution may not work for you, I figured that I didn't need to bother making typeform work with react, since I wanted to display it at the bottom of my page, I just followed the guidelines using "TypeForm Embed" documentation and ended up adding it outside of the react app, directly in the html body:

<body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>

    <div class="typeform-widget" data-url="https://fsf-sl.typeform.com/to/YOUR_ID"
         style="width: 100%; height: 700px;"></div>
    <script> (function () {
      var qs,
        js,
        q,
        s,
        d = document,
        gi = d.getElementById,
        ce = d.createElement,
        gt = d.getElementsByTagName,
        id = 'typef_orm',
        b = 'https://embed.typeform.com/';
      if (!gi.call(d, id)) {
        js = ce.call(d, 'script');
        js.id = id;
        js.src = b + 'embed.js';
        q = gt.call(d, 'script')[0];
        q.parentNode.insertBefore(js, q);
      }
    })(); </script>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>

But, I believe you can do something similar and inject this in a React component componentDidMount()

maario commented 5 years ago

I am working with NextJS when I came across this project and tried to use it to solve some of my problems with Typeform. Well this does not solve those as it is not compatible with SSR. I found although workaround which is something I can live with. I ditched this library in a first place. Then I added <script>-tag provided by typeform to custom _document.js -file inside <body>. Then I can call inside componentDidMount() window.typeformEmbed which gives me all features the Typeform SDK offers.

Quick guide to use Typeform Popup

Step 1: Add <script type="text/javascript" src="https://embed.typeform.com/embed.js" /> to your _document.sj -file

Step 2: Create component inside componentDidMount() const typeformComponent = window.typeformEmbed.makePopup(formURL, yourCustomOptions);

Step 3: When you want it just call typeformComponent.open() to show the form

In my case I store the value of makePopup() to state and call it later on to open the form. You could just set in options given to makePopup() open automatically by adding autoOpen: true

alexgarces commented 5 years ago

Hello @maario @Vadorequest actually there's a quite simple workaround for using it on Next.js on client side only: dynamic imports!

Here's an example that is working for me with the current version of react-typeform-embed.

import React from 'react';
import dynamic from 'next/dynamic';

const ReactTypeformEmbed = dynamic(() =>
  import('react-typeform-embed/lib/ReactTypeformEmbed'), {
    ssr: false
  }
);

export default () => (
  <div>
    <ReactTypeformEmbed url="https://demo.typeform.com/to/njdbt5" />
  </div>
);

Sorry about the ugly import path, I had to do it this way because it seems like dynamic imports don't support named exports (only default ones).

joebentaylor commented 5 years ago

Is there a way to do this in Gatsby @alexgarces ?

nuclearspike commented 5 years ago

I approached it a different way that doesn't require any additional packages or including the embed js code. It's true that just doing a regular 'import' of the package (and not rendering it until the client) will still cause SSR errors! HOLY S#!T This package is amateur-hour by having 'window' references even before the render of the component so that we have to workaround their incompetence. Do they not care about SSR at all? Every site that cares about SEO (any non-hobby site) always considers SSR because their site must be SSR or not get indexed by search engines. I'd first tried to only render the component on the client, but that wasn't enough. You cannot even import the component in server-side-code without errors cropping up (but not even every time. You'll change something, it works, you deploy, and then it doesn't work anymore). This package is poison by a very amateur developer, clearly. Was this a class-project by someone who has never worked in a company that requires SEO/SSR compatibility? The package doesn't even have the component as the default export. Come on, man! Step it up!

export default class TypeFormForm extends Component {
  static propTypes = {
    height: PropTypes.number,
  }

  static defaultProps = {
    height: 600,
  }

  constructor(props) {
    super(props)
    this.state = { Form: null }
  }

  componentDidMount() {
    const Form = require('react-typeform-embed').ReactTypeformEmbed
    this.setState({ Form })
  }

  render() {
    const { height, ...formProps } = this.props
    const { Form } = this.state;
    return (
      <div style={{ height }}>
        {Form && (
          <Form
            {...formProps}
          />
        )}
      </div>
    )
  }
}

So, basically, the react-typeform-embed package is not required/initialized until the client code is ready to render it. All TypeForm props are passed through to the component. A well-written package wouldn't need a wrapper, hopefully the author upgrades this package eventually to make it SSR-ready to appeal to professionals. If you're using a full screen pop-up, you won't want to set the height on the containing div. I put that there so that the server renders the page with a space for the form so that it doesn't suddenly reflow the page when the form comes in as the client renders. So, this is tailored to my usage, you may need to change things for how you are using typeform on your site.

I really should just make my own typeform component using their npm library, one that actually works with SSR.

Thanks to the author for making everyone your alpha testers without any regard for actual companies needing SEO/SSR using this and for announcing to the world that you just graduated!

dexterthemsb commented 3 years ago

I've been working with Next.js for a long time. There is a work around for this for static site generators like Next.js. As we know window is not accessible in a server we can access it after the component is mounted in the client side.

Add the script CDN in the custom document js file (_document.js in Next.js). I hope similar approach can be taken with other static site generators as well.

For eg. Gatsby - https://www.gatsbyjs.com/docs/custom-html/

<script type="text/javascript" src="https://embed.typeform.com/embed.js" />

Go to the page where you want to add the typeform. Use componentDidMount or useEffect to access the window object and add the typeform element to the window object itself.

useEffect(() => {
    window.typeform = window.typeformEmbed.makePopup(url, options);
  }, []);

Use onClick listener on the button and use the open function () => window.typeform.open()

You can use other functions as well, I've not tried them. Just pass the arguments accordingly and use refs where you have to pass the elements as an argument.