question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

add a `<Node>` wrapper component

See original GitHub issue

Right now using renderNode is kind of nice how simple it is to just return a component. But a few things about it are awkward:

  • Remembering to add props.attributes. This is required for findDOMNode to work to get the DOM node to do things like position placeholders/menus absolutely. Not too tedious, but easy to forget. (Could be solved by asserting that it exists I think.)
  • Adding your own shouldComponentUpdate that’s performant. This one is trickier, since it’s easy to do shallowEqual, but that’s not actually the most performant thing, since state is passed to all nodes, and if the selection changes, then shallowEqual with still cause an update.
  • Adding your own <Placeholder> text. This one is not horrible right now, but it’s slightly more complex that it needs to be for the 90% use case. The <Placeholder> component gives you flexibility to add placeholders in places like image captions, where even though the document has text, you actually want to trigger the placeholder based on whether a specific block has text. But the most common case is going to be just going it for the whole document.
  • Using the <Void> wrapper. This isn’t strictly an issue right now, it’s just slightly more verbose than needed, and if it happens to be made easier all the more reason. It’s required to make void nodes handled properly in contenteditable, and to allow you to style based on the :focus of the nested void.

To solve this, I was thinking about a <Node> component. (Or potentially two separate ones: <Block> and <Inline>.) It would look like:

function QuoteBlock(props) {
  return (
    <Node>
      <blockquote>{props.children}</blockquote>
    </Node>
  )
}

Instead of the current:

function QuoteBlock(props) {
  return (
    <blockquote {...props.attributes}>{props.children}</blockquote>
  )
}

The downside is that you have to always wrap things returned from renderNode in <Node>. But the upside is that it’s harder to screw things up in the common use cases:

Attributes

<Node> would handle adding the props.attributes to the highest component for you (potentially only if a child doesn’t already have it added). You could override this by just adding them deeper:

Current:

function CodeBlock(props) {
  return (
    <pre><code {...props.attributes}>{props.children}</code></pre>
  )
}

With <Node> general case:

function CodeBlock(props) {
  return (
    <Node>
      <pre><code>{props.children}</code></pre>
    </Node>
  )
}

With <Node> override case:

function CodeBlock(props) {
  return (
    <Node>
      <pre><code {...props.attributes}>{props.children}</code></pre>
    </Node>
  )
}

shouldComponentUpdate

<Node> would automatically add it’s own generally most performant shouldComponentUpdate function, for the 99% cases. Which you could override if you needed to with a suppressShouldComponentUpdate prop (mimicking React’s native suppress props):

Current:

class CodeBlock extends React.Component {
  shouldComponentUpdate(props, state) {
    ...
  }
  render() {
    return (
      <pre><code>{props.children}</code></pre>
    )
  }
}

With <Node> general case:

function CodeBlock(props) {
  return (
    <Node>
      <pre><code>{props.children}</code></pre>
    </Node>
  )
}

With <Node> override case:

class CodeBlockBody extends React.Component {
  shouldComponentUpdate(props, state) {
    ...
  }
  render() {
    return (
      <pre><code>{props.children}</code></pre>
    )
  }
}

function CodeBlock(props) {
  return (
    <Node suppressShouldComponentUpdate>
      <CodeBlockBody {...props} />
    </Node>
  )
}

Placeholders

<Node> could also expose an easy way to get the most general placeholder support with a placeholder attribute that takes a String || Element. Which could be overridden by just using the built-in <Placeholder> component yourself:

Current:

function QuoteBlock(props) {
  return (
    <blockquote>
      <Placeholder {...props} parent={props.node}>...</Placeholder>
      {props.children}
    </blockquote>
  )
}

With <Node> general case:

function QuoteBlock(props) {
  return (
    <Node placeholder="Enter some text...">
      <blockquote>{props.children}</blockquote>
    </Node>
  )
}

With <Node> override case:

function QuoteBlock(props) {
  return (
    <Node>
      <blockquote>
        <Placeholder {...props} parent={props.node}>...</Placeholder>
        {props.children}
      </blockquote>
    </Node>
  )
}

Void

<Node> could handle the isVoid wrapping automatically, but still expose the className and style attributes that are critical for being able to style the void’s contenteditable wrapper’s :focus styles:

Current:

function ImageBlock(props) {
  return (
    <Void className="image-wrapper">
      <img src={props.node.data.get('src')} />
    </Void>
  )
}

With <Node>:

function ImageBlock(props) {
  return (
    <Node className="image-wrapper">
      <img src={props.node.data.get('src')} />
    </Node>
  )
}

If you’re already using these types of use cases let me know what you think!

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:4
  • Comments:19 (12 by maintainers)

github_iconTop GitHub Comments

3reactions
mathieudutourcommented, Jul 23, 2016

how about a high order component (a la connect) instead of a component that need to be imported in your renderer?

Pros:

  • all the ones from a Node component
  • keep the renderer independent from slate
1reaction
mathieudutourcommented, Jul 25, 2016

Effectively it’s the same, conceptually it’s completely different:

  • in one case (Node), you need to tie your component to slate on the render function (bad)
  • in the other, you compose .

You can even do something like this:

const NODES = bindAttributesToRenderers({
  code: MyComponent
})

where bindAttributesToRenderers would iterate over the keys of the object and call connect on them (same way you have bindActionCreators in redux).

But I don’t like connect because it is already use in redux. So if I use redux and slate at the same time (likely), I won’t know which one is which.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How To Create Wrapper Components in React with Props
To create wrapper components, you'll first learn to use the rest and spread operators to collect unused props to pass down to nested...
Read more >
How to access child nodes of a React components to add a ...
I would like to be able to add a wrapper component to each of this component's child nodes in the list. If I...
Read more >
Adding a wrapper component to your Next.js app - Flavio Copes
How to use a wrapper component and add a common sidebar or header to it. ... One is using a Higher Order Component,...
Read more >
Custom Nodes Guide - React Flow
A custom node is a React component that is wrapped to provide basic functionality like selecting or dragging. From the wrapper component we...
Read more >
React tutorial for beginners 2021 - Understanding Wrapper ...
In this tutorial video I talk about what Wrapper Components are and how you use them in React.js. It's a lengthy video on...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found