add a `<Node>` wrapper component
See original GitHub issueRight 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 forfindDOMNode
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 doshallowEqual
, but that’s not actually the most performant thing, sincestate
is passed to all nodes, and if the selection changes, thenshallowEqual
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 incontenteditable
, 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:
- Created 7 years ago
- Reactions:4
- Comments:19 (12 by maintainers)
Top GitHub Comments
how about a high order component (a la connect) instead of a component that need to be imported in your renderer?
Pros:
Node
componentEffectively it’s the same, conceptually it’s completely different:
You can even do something like this:
where
bindAttributesToRenderers
would iterate over the keys of the object and callconnect
on them (same way you havebindActionCreators
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.