Slate: Server side rendering doesn't work.

Created on 6 Jun 2017  Â·  18Comments  Â·  Source: ianstormtaylor/slate

Hello, thanks for the great work you are doing here. I have a minor issue, due to get-window (https://github.com/webmodules/get-window) library using in several places in the core (plugins, models, etc.), slate doesn't work in the server-side, get-window using browser only variables like window and document, I understand is using to detect if the window is an iframe.

Best regards.

âš‘ needs info

Most helpful comment

@rkrueger11 yes you need to reset the key generating function.
I just created a next.js example for this.

Next.js-Example

https://github.com/zeit/next.js/pull/4899

SSR-Example

import React from 'react'
import Plain from 'slate-plain-serializer'
import { Editor } from 'slate-react'
import { KeyUtils } from 'slate'

class Index extends React.Component {
  constructor (props) {
    super(props)

    // In order to allow ssr we need to reset the key
    // generating function to its initial state.
    KeyUtils.resetGenerator()

    // Deserialize the initial editor value.
    this.state = {
      value: Plain.deserialize(
        'This is editable plain text, just like a <textarea>!'
      )
    }
  }

  render () {
    return (
      <Editor
        placeholder='Enter some plain text...'
        value={this.state.value}
        onChange={this.onChange}
      />
    )
  }

  onChange = ({ value }) => {
    this.setState({ value })
  }
}

export default Index

@ianstormtaylor I think this issue can be closed.

All 18 comments

I also really need server-side rendering to work (with next.js)

@Antherkiv can you paste an example or a stacktrace of the error you're getting, so people tackling this issue have a starting point? It's not immediately clear how this would break, since window methods tend to be used in response to user interaction.

If you need a quick workarond. You can import your slate editor with dynamic imports.
This allows you to render the Editor Component on client only.

```js
import React from 'react'
import dynamic from 'next/dynamic'

const DynamicLoadedEditor = dynamic(
import('./MyAwesomeSlateEditor'),
{
loading: () => (

loading...

),
ssr: false
}
)

export default DynamicLoadedEditor
````

@HaNdTriX Thanks for the idea.

Just to clarify. I am using slate with next.js and it works like a charm.
The markup is rendered on the server without my workaround above.
Thanks for the great work @ianstormtaylor!

@HaNdTriX Did you have to do anything special to get it to render server side w/ Next.js? I keep getting an error: Unable to find a DOM node for "3". This is often because of forgetting to addprops.attributesto a custom component.

@rkrueger11 yes you need to reset the key generating function.
I just created a next.js example for this.

Next.js-Example

https://github.com/zeit/next.js/pull/4899

SSR-Example

import React from 'react'
import Plain from 'slate-plain-serializer'
import { Editor } from 'slate-react'
import { KeyUtils } from 'slate'

class Index extends React.Component {
  constructor (props) {
    super(props)

    // In order to allow ssr we need to reset the key
    // generating function to its initial state.
    KeyUtils.resetGenerator()

    // Deserialize the initial editor value.
    this.state = {
      value: Plain.deserialize(
        'This is editable plain text, just like a <textarea>!'
      )
    }
  }

  render () {
    return (
      <Editor
        placeholder='Enter some plain text...'
        value={this.state.value}
        onChange={this.onChange}
      />
    )
  }

  onChange = ({ value }) => {
    this.setState({ value })
  }
}

export default Index

@ianstormtaylor I think this issue can be closed.

Thanks @HaNdTriX!

There is now an offical next.js example: https://github.com/zeit/next.js/tree/canary/examples/with-slate

@HaNdTriX It works but when you clear the inputbox and type something on the field again it gives the error: Unable to find a DOM node for "2". This is often because of forgetting to addprops.attributes to a custom component.

EDIT: Only happens in express custom server.

EDIT 2: Fixed! for some weird reason it works when you close chrome dev tools.

I've got multiple instances of slate on a page, and @HaNdTriX's fix won't work for me.

Whenever I try and focus one of the slate instances, it will always focus the first one. https://codesandbox.io/s/zkzx818ryx. I presume this is because the keys are being reused across instances.

I'm not sure of a fix that will work with SSR and multiple instances. The global nature of KeyUtils isn't ideal.

Any ideas?

If you need a quick workarond. You can import your slate editor with dynamic imports.
This allows you to render the Editor Component on client only.

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

const DynamicLoadedEditor = dynamic(
  import('./MyAwesomeSlateEditor'),
  {
    loading: () => (<p>loading...</p>),
    ssr: false
  }
)

export default DynamicLoadedEditor

thank, this's really works

@tuanhuu

thank, this's really works

Yeah I'll have to do this for now I think, but it's not really a fix.

@ianstormtaylor should I raise another issue for this?

Just tried the quick fix disabling SSR for the editor component in React, it works! But still waiting for a proper fix that works with SSR @ianstormtaylor

The KeyUtils.resetGenerator() works for slate instances that rendered with SSR, but crash for slate instances that are later generated on the client side (got max callstack error once I focus on the slate editor)

Here is how I quick fix it for now:

import React from "react"
let SlateEditor

class Editor extends React.Component {
  componentDidMount() {
    SlateEditor = require("./SlateEditor").default
  }

  render() {
    if (SlateEditor) {
      return (
        <SlateEditor
          {...this.props}
        />
      )
    }
    return null
  }
}

export default Editor

@HaNdTriX any tips on getting this to work with multiple slate instances?

@juhaelee the issue is that data-key are starting from 0 for each instance of the Editor due to KeyUtils.resetGenerator(). So when clicking on an Editor, it focuses to the first data-key of the dom, thus being the first Editor instance and the second one becomes unfocusable.

Slate provides a KeyUtils.setGenerator(myKeygenFunction) to pass our own key generator. This gives us the opportunity to create truly unique keys across Editor instances.

In the parent that imports this component, pass a different idFromParentIteration prop for each instance of PlainText component and you should be good. Like so:

['first-editor', 'second-editor'].map((name, idx) => <PlainText idFromParentIteration={name + idx} />)

And here's a complete example with a custom key generator.

import React from "react";
import Plain from "slate-plain-serializer";
import { KeyUtils } from 'slate';
import { Editor } from "slate-react";

const initialValue = Plain.deserialize(
  "This is editable plain text, just like a <textarea>!"
);

class PlainText extends React.Component {
  constructor(props) {
    super(props);
    let key = 0;
    const keygen = () => {
      key += 1;
      return props.idFromParentIteration + key; // custom keys
    };
    KeyUtils.setGenerator(keygen);
  }
  render() {
    return (
      <Editor
        placeholder="Enter some plain text..."
        defaultValue={initialValue}
      />
    );
  }
}

export default PlainText;

@rklhui I'm facing the same issue and have to disable SSR to have the editor not crash in editing mode. Did you figure out a different solution?

What is the solution in Slate 0.50+ now that KeyUtils has been removed?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ezakto picture ezakto  Â·  3Comments

yalu picture yalu  Â·  3Comments

gorillatron picture gorillatron  Â·  3Comments

Slapbox picture Slapbox  Â·  3Comments

JSH3R0 picture JSH3R0  Â·  3Comments