Skip to content
This repository was archived by the owner on Jun 19, 2018. It is now read-only.

Conversation

zetlen
Copy link
Contributor

@zetlen zetlen commented Jun 7, 2018

This PR is a:

[x] New feature
[ ] Enhancement/Optimization
[ ] Refactor
[ ] Bugfix
[ ] Test for existing code
[ ] Documentation

Summary

PageBuilder CMS, like many CMSes, will provide raw HTML as a server
response. This PR adds a RichContent component to handle that common use
case.

When this pull request is merged, it will...

Add the RichContent component, its stories, docs, and tests, and expose it as an exported property at the root.

Additional information

  • It uses the dangerouslySetInnerHTML API, rendering a string of
    HTML supplied as a prop.
  • If the outer system replaces the raw HTML with progressively
    enhanced functionality, such as a tree of live ReactElements,
    RichContent accepts ReactElements as children. Children will
    override the sanitizedRawHtml prop.

@magento-cicd2
Copy link

magento-cicd2 commented Jun 7, 2018

CLA assistant check
All committers have signed the CLA.

@PWAStudioBot
Copy link

PWAStudioBot commented Jun 7, 2018

All tests passed! View Coverage Report

A Storybook for this PR has been deployed!. It will be accessible as soon as the current build completes.

Generated by 🚫 dangerJS

render() {
return (
this.props.children || (
<span
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be a fragment, since the contents below the span could be block level elements

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good eye, I'll make that change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, turns out you can't dangerouslySetInnerHTML on a Fragment, so it'll have to be a wrapper element. But I'll let 'em specify what it is, and allow props spread.

@DrewML
Copy link
Contributor

DrewML commented Jun 11, 2018

Leaving @jimbo's Slack comment here:

image

I don't know that the name RichContent satisfies this.

return (
this.props.children || (
<span
className="peregrine-raw-html"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not encouraging use of global CSS, so I don't think it's a good idea to have a generic class on here. Would prefer className={this.props.className}

expect(wrapper.html()).toEqual(
'<span class="peregrine-raw-html"><h1 id="o-no">Raw!!!</h1></span>'
);
wrapper = mount(
Copy link
Contributor

@DrewML DrewML Jun 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a separate test case. It's important that, if this one assertion fails, the description of the test failing is clear. Right now, if we have a bug that favors the null children, the name of the test failing would be Renders raw HTML, which isn't descriptive of what we're testing (specifically, that we ignore null children and continue to favor the sanitizedRawHtml prop).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point; I've changed it.

@DrewML
Copy link
Contributor

DrewML commented Jun 11, 2018

(As you probably already know), I am a big fan of keeping API surfaces as small as we possibly can until we have an actual need for an API. In this case, I'm not convinced we need the children enhancement behavior.

How do you feel about sticking to just the prop until we have a real-world reason to accept children?

@zetlen
Copy link
Contributor Author

zetlen commented Jun 12, 2018

  1. I chose the RichContent name because:

    • React isn't designed to display HTML from elsewhere
    • Magento's CMS functionality creates and stores structured content (serialized HTML from the WYSIWYG and/or PageBuilder) on the server sid
    • Including such content will be a pretty common use case, so the component name should be short and smooth
    • The component is for displaying content with rich capabilities, even if that just means "formatting"

    I considered RawHTML and ContentWithFallback, but I thought both of them would get a few more double takes from devs than RichContent would.

    I'm happy to change it for a better suggestion, though. @DrewML @jimbo @jcalcaben do you have one?

  2. Thanks for the test suggestion; results will definitely be more informative now.

  3. The API I wrote is what I think is minimally viable for the PageBuilder integration strategy we currently have. In that proposal, raw HTML comes down first and is then replaced with live implementations once their components and data load.

    Still, I could remove the children override from the API like you're suggesting, because it would still have business value for legacy WYSIWYGs. I'll make that change and push it (and save the children API for later, if our current PB integration path stays the same).

PageBuilder CMS, like many CMSes, will provide raw HTML as a server
response. Added a `RichContent` component to handle this common use
case.

 - It uses the `dangerouslySetInnerHTML` API, rendering a string of
   HTML supplied as a prop.
 - If the outer system replaces the raw HTML with progressively
   enhanced functionality, such as a tree of live `ReactElements`,
   `RichContent` accepts `ReactElements` as children. Children will
   override the `sanitizedRawHtml` prop.

feat(RichContent): Add configurability to wrapper tag

 - Added two props to configure the "parent element" that React requires
 to `dangerouslySetInnerHTML`:
    - `wrapperTag` to specify the tag name as a string
    - `wrapperProps` to pass an object of props to that tag
 - Updated docs, stories, and tests to reflect this change

feat:retract children API for later
@zetlen zetlen force-pushed the zetlen/rich-content-component branch from d19436d to 20d2b3b Compare June 12, 2018 13:27
@DrewML
Copy link
Contributor

DrewML commented Jun 12, 2018

Still, I could remove the children override from the API like you're suggesting, because it would still have business value for legacy WYSIWYGs

That would be great. And not just "legacy WYSIWYG" - this is useful today for Category Description on category pages, which can (and frequently do) contain raw markup.

I'm all for the fallback thing when we get there. I just don't want to take a stab at the API now, because if we're wrong, we're stuck supporting it + something new.

};

render() {
// JSX tags must be in PascalCase
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, this is only accurate for bare identifiers. Member expressions do not have this limitation.

image


static defaultProps = {
wrapperTag: 'span',
wrapperProps: {
Copy link
Contributor

@DrewML DrewML Jun 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I've seen, it's more common in community React components to just spread any non-own prop into the wrapper in this case. So your render would be:

render() {
    const { wrapperTag: Tag, ...others } = this.props;
	return (
		<Tag className='peregrine-raw-html' {...others} />
	);
}

and you drop wrapperProps entirely

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm so accustomed to not using object spread for support reasons that I forgot it was an option. I'll change the API.

@@ -0,0 +1 @@
export { default as RichContent } from './RichContent';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also be re-exported in src/index.js

@brendanfalkowski
Copy link

brendanfalkowski commented Jun 12, 2018

I considered RawHTML and ContentWithFallback, but I thought both of them would get a few more double takes from devs than RichContent would.

RichContent feels like marketing-speak and I'd personally double-take what that is exactly. MS WordPad, anyone? RawHTML or PlainHTML or just HTML seem to describe what's in the tin better.

@DrewML
Copy link
Contributor

DrewML commented Jun 12, 2018

I'll throw my vote in for RawHTML - Plain doesn't seem super clear to me.

@zetlen
Copy link
Contributor Author

zetlen commented Jun 13, 2018

@brendanfalkowski @DrewML Thanks for the feedback, folks. I may be trying to solve a problem of a repetitive pattern I'm anticipating, one that would look a little like this:

 <Query query={CMS_CONTENT} variables={{ id }}>
    {({ loading, error, data }) => {
      if (loading) return null;
      if (error) return `Error!: ${error}`;
      if (data.children) {
          return data.children;
      }
      return (<RawHtml html={data.raw} />)
    }}
  </Query>

But that may end up not being a big hassle. I'll change the component name to RawHtml.

@zetlen zetlen force-pushed the zetlen/rich-content-component branch from 280d8d2 to f6f27a6 Compare June 13, 2018 09:35
 - Replace `wrapperProps` with an object spread that automatically
 removes the custom props from the remaining object

 - Export component from root
@zetlen zetlen force-pushed the zetlen/rich-content-component branch from a0e78a3 to f0b4217 Compare June 13, 2018 09:53
@DrewML
Copy link
Contributor

DrewML commented Jun 13, 2018

This PR will need to be re-targeted to the new monorepo.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants