Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gatsby-transformer-contentful-richtext use with React? #46

Closed
sarahbethfederman opened this issue Dec 13, 2018 · 52 comments
Closed

gatsby-transformer-contentful-richtext use with React? #46

sarahbethfederman opened this issue Dec 13, 2018 · 52 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@sarahbethfederman
Copy link

I am wondering what is the correct way to use react with the plugin configuration.

I need to use a gatsby Link component for INLINES.ENTRY_HYPERLINK and not sure how to do so from the gatsby-config file or if there is some other workaround. I have lots of other use cases for using a react component as well.

Can anyone point me in the right direction?

@Khaledgarbaya
Copy link
Contributor

Hey @sarahbethfederman,
the transformer plugin only works with raw HTML.
If you want to render React component you query the raw data and at rendering, you can use your React components.

Cheers,
Khaled

@sarahbethfederman
Copy link
Author

Is there any documentation on how to do this with gatsby? I don't know how to query for a rich text field in graphql without the plugin because it seems you have to specify all the fields and nesting

@sarahbethfederman
Copy link
Author

sarahbethfederman commented Dec 14, 2018

OK so I figured out how to get the string:

childContentfulRichText {
            internal {
              content
            }
}

and then use that with a custom renderer like so:

import React from 'react'
import { documentToHtmlString } from '@contentful/rich-text-html-renderer';
const { INLINES, BLOCKS, MARKS } = require('@contentful/rich-text-types');

const options = {
  renderNode: {
    [BLOCKS.PARAGRAPH]: (node, next) => `<p class='spectrum-Body3'>${next(node.content)}</p>`,
    [INLINES.HYPERLINK]: (node, next) => {
      return `<a class='spectrum-Link' href="${node.data.uri}">${next(node.content)}</a>`
    },
    [INLINES.ENTRY_HYPERLINK]: (node, next) => {
      // TODO figure out how to use gatsby link
      return `<a class='spectrum-Link' href=''>${next(node.content)}</a>`
    }
  }
}
class RichTextRenderer extends React.Component {
  render() {
    const { content } = this.props;
    const JSONContent = JSON.parse(content);
    const article = documentToHtmlString(JSONContent, options);
    return <div dangerouslySetInnerHTML={{ __html: article }} />;
  }
}

RichTextRenderer.propTypes = {};

export default RichTextRenderer;

My questions are now:

  • how do I grab the entry from the ID I get from the entry_hyperlink node?
  • still not grasping how to wrap the nodes with a react component

@sarahbethfederman
Copy link
Author

Ok update! I figured this out. It seems to be working.
I made a documentToJSX function instead and replaced the documentToHTML function in the above.

import escape from 'escape-html';
import React from 'react';

import {
  Document,
  Mark,
  Text,
  BLOCKS,
  MARKS,
  INLINES,
  Block,
  Inline,
  helpers,
} from '@contentful/rich-text-types';

const defaultNodeRenderers = {
  [BLOCKS.PARAGRAPH]: (node, next) => <p>{next(node.content)}</p>,
  [BLOCKS.HEADING_1]: (node, next) => <h1>{next(node.content)}</h1>,
  [BLOCKS.HEADING_2]: (node, next) => <h2>{next(node.content)}</h2>,
  [BLOCKS.HEADING_3]: (node, next) => <h3>{next(node.content)}</h3>,
  [BLOCKS.HEADING_4]: (node, next) => <h4>{next(node.content)}</h4>,
  [BLOCKS.HEADING_5]: (node, next) => <h5>{next(node.content)}</h5>,
  [BLOCKS.HEADING_6]: (node, next) => <h6>{next(node.content)}</h6>,
  [BLOCKS.EMBEDDED_ENTRY]: (node, next) => <div>{next(node.content)}</div>,
  [BLOCKS.UL_LIST]: (node, next) => <ul>{next(node.content)}</ul>,
  [BLOCKS.OL_LIST]: (node, next) => <ol>{next(node.content)}</ol>,
  [BLOCKS.LIST_ITEM]: (node, next) => <li>{next(node.content)}</li>,
  [BLOCKS.QUOTE]: (node, next) => <blockquote>{next(node.content)}</blockquote>,
  [BLOCKS.HR]: () => <hr />,
  [INLINES.ASSET_HYPERLINK]: node => defaultInline(INLINES.ASSET_HYPERLINK, node),
  [INLINES.ENTRY_HYPERLINK]: node => defaultInline(INLINES.ENTRY_HYPERLINK, node),
  [INLINES.EMBEDDED_ENTRY]: node => defaultInline(INLINES.EMBEDDED_ENTRY, node),
  [INLINES.HYPERLINK]: (node, next) => <a href={node.data.uri}>{next(node.content)}</a>,
};

const defaultMarkRenderers = {
  [MARKS.BOLD]: text => <b>{text}</b>,
  [MARKS.ITALIC]: text => <i>{text}</i>,
  [MARKS.UNDERLINE]: text => <u>{text}</u>,
  [MARKS.CODE]: text => <code>{text}</code>,
};

const defaultInline = (type, node) => <span>type: {type} id: {node.data.target.sys.id}</span>;

/**
 * Serialize a Contentful Rich Text `document` to JSX.
 */
export function documentToJSX(richTextDocument, options = {}) {
  return nodeListToJSX(richTextDocument.content, {
    renderNode: {
      ...defaultNodeRenderers,
      ...options.renderNode,
    },
    renderMark: {
      ...defaultMarkRenderers,
      ...options.renderMark,
    },
  });
}

function nodeListToJSX(nodes, { renderNode, renderMark }) {
  return nodes.map(node => nodeToJSX(node, { renderNode, renderMark }));
}

function nodeToJSX(node, { renderNode, renderMark }) {
  if (helpers.isText(node)) {
    const nodeValue = escape(node.value);
    if (node.marks.length > 0) {
      return node.marks.reduce((value, mark) => {
        if (!renderMark[mark.type]) {
          return value;
        }
        return renderMark[mark.type](value);
      }, nodeValue);
    }

    return nodeValue;
  } else {
    const nextNode = nodes => nodeListToJSX(nodes, { renderMark, renderNode });
    if (!node.nodeType || !renderNode[node.nodeType]) {
      return null;
    }
    return renderNode[node.nodeType](node, nextNode);
  }
}

only remaining question is what is the best what to grab the entry from the ID

@Khaledgarbaya
Copy link
Contributor

hey @sarahbethfederman usually when you use the SDK to grab the content the entry should be resolved automatically for you. So you will have access to entry.fields

@sarahbethfederman
Copy link
Author

sarahbethfederman commented Dec 15, 2018 via email

@Khaledgarbaya
Copy link
Contributor

hey @sarahbethfederman the gatsby-source-contentful is using the SDK under the hood, so it should resolve the entries for you

@sarahbethfederman
Copy link
Author

It doesn't seem to, all I see is the id. How do I check and are there any workarounds?

@polarathene
Copy link

@sarahbethfederman Thanks for putting together that documentToJSX method :) Hopefully it'll become a part of the plugin. Although I'm not sure how you should override it from gatsby-config.js due to the difference between node require() statements vs browser import statements? I know some projects work around that with Babel, but could trip some users up that don't.

I'm not sure if my approach to converting regular internal hyperlinks into Gatsby Links is much different from the entry kinds you're trying to get working, but maybe it'll be of help to you? I'm parsing the html string into JSX presently rather than using your documentToJSX method(not sure how to get it working with the Link component due to the require/import issue). The logic to convert an anchor tag to a Link component is taken from the Gatsby docs here.

@sarahbethfederman
Copy link
Author

sarahbethfederman commented Dec 21, 2018

Oh interesting solution! I'll have to check that out. I'm not using the configuration in gatsby-config.js because I couldn't figure out how to add react to it, and it doesn't really play nice with gatsby's tooling there (no hot reloads or anything). I'm importing the RichTextRenderer component from above and replaced the line with documentToHTML with documentToJSX, replaced the div with the JSX returned from that, and added a prop to override the component config for specific uses (like telling it to use a different heading class when rendering h3s in the sidebar). It just checks for the prop and returns { options, ...prop.options}.

The other issue I ran into was because my function returns an array of JSX, I get the each array item must have a unique key warning and haven't figured out a way to add that in, so it's just annoying atm.

The internal links method is probably a better solution than what I was doing which was just using <a> on hyperlinks and <Link> on entry_hyperlinks, thanks for that.

Anyway, would definitely be good to have this built in to the plugin! I stripped out all the typescript defs from the original documentToHTML function because we aren't using typescript so it'd need a little work, but happy to try to PR when I have a bit of time (or you're welcome to).

@Khaledgarbaya
Copy link
Contributor

Hey @sarahbethfederman

It doesn't seem to, all I see is the id. How do I check and are there any workarounds?

I think it does work only on the initial sync, when deleting the .cache folder.
Mainly because the SDK might not have enough data in the payload to resolve these links.

@sarahbethfederman
Copy link
Author

Yikes. That's a pretty massive bug. Do you have any workarounds?

@polarathene
Copy link

Yikes. That's a pretty massive bug. Do you have any workarounds?

I'm guessing it's due to not having information like when the entity/data was last modified? It's also why I can't ensure that my local/offline support is correct with what's on Contentful.

There's also a similar problem I ran into early into my Contentful use with Gatsby, the gatsby-source-contentful plugin cached my ContentType for a web page content, which linked to other CMS data like ContentfulAssets, which I hadn't actually published, so in my GraphQL query I wasn't able to retrieve those... publish them then duh! Nope lol, while they did in fact change, the ContentType that was linking to them and was cached hadn't been touched/updated as far as gatsby-source-contentful(or Contentful itself? Not sure where it gets the information about a change) was concerned.

Perhaps that's the same issue with RichText and and these embedded links? (though I'm confused why they work initially the first time but that data isn't kept afterwards instead of just not able to update like my ContentType with unpublished linked assets experience). I can't actually recall if deleting the cache fixed it or if I had to make some change on Contentful to the ContentType and undo it after to force a changed state(I think this resolved it). I know that with my case, GraphQL schema ignores the fields if none of the results have any data(eg all would return null) which gave added confusion as I knew the field existed on my ContentType.

Does @Khaledgarbaya work at Contentful? If it's a bug on Contentful's end where we can do nothing about it, is avoiding a workaround possible with an actual fix? Surely these would be bugs that can affect Contentful users on paid plans too?

@Khaledgarbaya
Copy link
Contributor

Hey @sarahbethfederman

Yikes. That's a pretty massive bug. Do you have any workarounds?

It is not a bug, as I mentioned the sync API is intended to only return the changed entities so if you don't change a reference it won't be included there and the SDK can't resolve the link unless it does an extra HTTP call which we decided to not do.

@polarathene

Perhaps that's the same issue with RichText and and these embedded links?

It can be yes.

Does @Khaledgarbaya work at Contentful?

😂 Yes I do.

@sarahbethfederman
Copy link
Author

I am very unclear how resolved entries disappearing is not a bug. Even if it is technically expected on your end, it's entirely unexpected behavior to your users. It makes it unusable to have unresolved entries. Again, is there a workaround you suggest? Do I have to abandon the plugin? Between the lack of react support and entries being unresolved, this plugin is proving to be very frustrating for seemingly basic use cases. Do you plan on fixing these issues or have a timeline in place? Is there an official support channel I can use? I'm on a deadline here and my org is a paying customer.

@polarathene is the entries being unresolved an issue with your method as well?

@polarathene
Copy link

Again, is there a workaround you suggest?

Deleting the cache should do the trick from what I understand, then the plugin has nothing to compare to and will pull the data down again fresh.

Between the lack of react support and entries being unresolved, this plugin is proving to be very frustrating for seemingly basic use cases.

I don't think it can offer React support in terms of overrides like it does for the HTML strings, unless you go with JSX strings like I did via the JSX parser on component/client side(instead of node). That's due to the conflicting approaches for importing modules afaik. I've been asked about contributing the JSX parser approach which is more of a workaround, you have to write the overrides as JSX wrapped in a string(potentially error prone) and you must also provide the RichText component with the components you intend to use with it(so all possible components you expect could be in the string content). It does work though and no problems so far.

Is there an official support channel I can use? I'm on a deadline here and my org is a paying customer.

As a paying customer it'd probably help raise priority for it :) Though if the JSX parser solution works for you, I could possibly have that contributed within the week(+however long it takes to get reviewed/merged), few other tasks to take care of first.


is the entries being unresolved an issue with your method as well?

Umm I don't have anything too fancy in my RichText content, just web and internal links. I could try reproduce something similar to what you're having trouble with and see though? My similar bug was unrelated to RichText, I had a ContentType with a field that links to ContentfulAssets(images) that I published without publishing the images. That resulted in the field for them being null and omitted in the GraphQL schema. Forcing an update on my ContentType entry via Contentful in the browser fixed that and I could query the field to get ContentfulAssets again.

@sarahbethfederman
Copy link
Author

sarahbethfederman commented Jan 3, 2019

The internal links you have are resolving properly?

Deleting the cache folder brings up this issue for me so that doesn't help unfortunately. #53
The only fix I've found for this is to unpublish all components, and republish them ~4 at a time, building inbetween. :(

@polarathene
Copy link

The internal links you have are resolving properly?

Well they are just text for me, a hyperlink to nothing related to Contentful, but a different url like mydomain.com/contact, mydomain.com/services.. except to make them internal gatsby links I would have /contact and /services instead.

When I've finished up some other todo items, I'll try out some internal Contentful links? Do you have any examples of what you're linking to? Just another ContentType or an Image asset?

@sarahbethfederman
Copy link
Author

So it looks like contentful only sends the link text for entry_hyperlinks from the API response. https://www.contentful.com/developers/docs/concepts/rich-text/

I don't understand how I'm supposed to link to something with just the link text. There's no way to access the slug unless I do a full embedded entry. I tried using the sys.id that's returned to manually get the entry from the SDK but the id doesn't refer to an existing contentful entry, so I'm not sure what the id is supposed to refer to. I'm going to have to convert every entry-hyperlink to an inline entry just to get the slug I guess.

@polarathene
Copy link

Where is the sadface emoji reaction? :(

@Khaledgarbaya if this is not something we can contribute a fix for, can you chime in ith Contentful's stance/plans on this?

I understand it's still a beta component and GraphQL is in beta too(though I think Gatsby's GraphQL is separate from that, haven't looked much into how the data is being queried from Contentful.

@Khaledgarbaya
Copy link
Contributor

Hey @sarahbethfederman,

So it looks like contentful only sends the link text for entry_hyperlinks from the API response. contentful.com/developers/docs/concepts/rich-text

if you take a closer look at the payload there is

data: {
    target: {
      sys: {
        id: "7GI6AkMKWIqiiUIuG0uAO",
        type: "Link",
        linkType: "Entry"
      }
    }
  }

that's the mbedded entry and the SDK be able to resolve the link, it will append
the fields object to the data.target

@sarahbethfederman
Copy link
Author

Oh yes I see, it just wasn't resolving. Are there plans to fix this so contentful can be used with gatsby develop? Right now I'm having to build every time I make a change to get entries resolved and it's slow without hot reloading. Here's the cmd for reference: nodemon --watch src --exec 'npm run deploy

@sarahbethfederman
Copy link
Author

Current working code if anyone else is working on this:

https://gist.github.com/sarahbethfederman/04613d376188f71a1995228f33c38328

@sarahbethfederman
Copy link
Author

It looks like nested embedded entries (embedded entries that include an embedded entry) are not resolved, is this being worked on by chance?

@polarathene
Copy link

@sarahbethfederman I'm not working on anything related to this presently, still chugging away on other tasks.

Looking forward to a response from @Khaledgarbaya about Contentful's stance on how it should be handled(assuming it matters to them to allocate paid hours to it, and/or an issue on their end that we're not able to resolve). He has not really responded about my queries specific to the company's involvement(and may not be able to? Blink twice if you're in danger! :o ).

I haven't looked into using Contentful as much as you are, perhaps you can setup a repo that reproduces the problem? Gatsby repo has a using-contentful example project that has some public secrets iirc, adding a rich-text example there might be a good idea?

@Khaledgarbaya
Copy link
Contributor

Based on this issue gatsbyjs/gatsby#10592

It looks like nested embedded entries (embedded entries that include an embedded entry) are not resolved, is this being worked on by chance?

is not an issue

@mgmolisani
Copy link
Contributor

I have also been looking into this for a project and commented on the separate issue #54 around this. One of my first contributions to any project so hopefully I am approaching all of this properly. I think the current working version makes assumptions that the developer is returning a React element in their callbacks and not just a text node or null.

The following should resolve it and feels a lot less hacky since it just uses React helpers. I also does not attempt to overwrite user input keys which might be desirable for something like preserving state by using an embedded assets id as a key:

import React, {isValidElement, cloneElement} from 'react';

const appendKeyToValidElement = (element, key) => {
  if (element && isValidElement(element) && element.key === null) {
    return cloneElement(element, {key});
  }
  return element;
};

full comment

Full working version in the links below:
index.js
full repo with tests reworked from html-renderer and some changes to test React specific renders

@Khaledgarbaya
Copy link
Contributor

@mgmolisani,
this looks amazing,
Please feel free to start a pull request and we can continue from there.

@mgmolisani
Copy link
Contributor

Also want to get opinions on adding the complete document AST to the gatsby-source-contentful schema for rich text. Since we can't put any JSX in our gatsby-config or -node files, the only way to use documentToReactTree in Gatsby (which was the initial question and my goal) would be calling it in the component tree, either directly or wrapped in the return of a <RichText> helper component. Either way, it is expecting a document object and feeding it JSON.parse(data.contentfulStuff.key.key) or JSON.parse(data.contentfulStuff.key.internal.content) (where key is the API name of the rich text field) just feels like extra steps to remember that could be solved on the source.

I'm no Gatsby plugin expert, but I try to do my homework. The following code in the gatsby-node.js file should add the full document tree as an object to the GraphQL node. Took the naming convention from the remark transformer package.

// gatsby-node.js
const { GraphQLJSON } = require('gatsby/graphql');

exports.setFieldsOnGraphQLNodeType = ({ type }) => {
  if (type.name.match(/contentful.*RichTextNode/)) {
    return {
      contentAST: {
        type: GraphQLJSON,
        resolve: source => {
          // Can also use source.content here, not sure what is best practice vs. most performant
          // or if there are gotchas with one or the other
          return JSON.parse(source.internal.content);
        }
      }
    };
  }
  return {};
};

Then, you could just use:

documentToReactTree(data.contenfulStuff.key.contentAST, options)

Not saying this saves a ton of time typing, but to me it feels cleaner than expecting the developer to parse JSON for use with Gatsby and potentially touching the internal field outside of a plugin. I'm sure there are other use cases for the AST as an object where the extra step of parsing is not desirable.

@mirshko
Copy link

mirshko commented Jan 24, 2019

@mgmolisani just using this now for a project. its working out quite nicely. nice work so far! excited to have this as native in the project

@mgmolisani
Copy link
Contributor

Here is a gist of the same code but with caching. I have no way to tell if caching has any improvement on load time so if you have a beefy rich text node, give it a go. I also noticed Gatsby uses a cache manager that says it resolves with what was stored in the cache... but it actually doesn't which you might better understand from my gist comments. I opened an issue with Gatsby here (which was confirmed in like minutes - go Gatsby!) if you want to help with that.

@mgmolisani
Copy link
Contributor

Having a pretty hard time getting the repo going as npm install on the forked monorepo doesn't seem to be successfully installing and linking the @contentful/rich-text-types lib. Possibly missing node_modules in @contentful/rich-text-types as well. All tests for libs that have that as a dep end up failing. This behavior is consistent on my PC and my Mac. Not sure if it is just me doing something wrong but this is pretty much step one and I'm pretty stuck. Is anyone else having trouble forking and npm install and npm run test? Followed the CONTRIBUTING.md as closely as I could. Seemed straightforward but hasn't worked for me. Help appreciated.

@Khaledgarbaya
Copy link
Contributor

Hi @mgmolisani, I think it's better to use yarn with the monorepo instead of npm

@mgmolisani
Copy link
Contributor

Still no luck with a new clone and yarn install.

Consistently getting this error in many tests:

1 import { Document, Node, Block, Link, NodeData } from '@contentful/rich-text-types';
                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Build seemed to be successful with only 1 hoisting warning.

$ lerna bootstrap --hoist --progress --no-ci
lerna notice cli v3.10.7
lerna info Bootstrapping 6 packages
lerna WARN EHOIST_ROOT_VERSION The repository root depends on @types/jest@^23.3.8, which differs from the more common @types/jest@^23.3.12.
lerna WARN EHOIST_PKG_VERSION "@contentful/rich-text-types" package depends on @types/jest@^23.3.12, which differs from the hoisted @types/jest@^23.3.8.
lerna info Installing external dependencies
lerna info hoist Installing hoisted dependencies into root
lerna info hoist Pruning hoisted dependencies
lerna info hoist Finished pruning hoisted dependencies
lerna info hoist Finished bootstrapping root
lerna info Symlinking packages and binaries
lerna info lifecycle @contentful/gatsby-transformer-contentful-richtext@13.0.0~prepare: @contentful/gatsby-transformer-contentful-richtext@13.0.0

Is there anything else I could provide that might help assess? Maybe I'm missing a global package? Any thoughts?

@janosh
Copy link

janosh commented Feb 1, 2019

Interesting discussion though it's hard for me to fully grasp

  • the issues that remain and
  • which approach turned out to work best for rendering Contentful rich text embedded entries using React components.

Could someone here address those questions?

@mgmolisani
Copy link
Contributor

Interesting discussion though it's hard for me to fully grasp

  • the issues that remain and
  • which approach turned out to work best for rendering Contentful rich text embedded entries using React components.

Could someone here address those questions?

  1. No real issues remain other than I am trying to work on it but having serious issues forking and installing the monorepo. Seems to fail on the types install. Would appreciate if someone else would try and let me know if they are having issues.

  2. Similar approach to documentToHTML. We tell it what to render in a callback given the raw node and the children post parsing. Keys can be manually set but non manually set keys get prop injected by cloning to valid elements. Be careful with setting keys manually as they should not be index-like (e.g. '1-key' wont clash while "1" might).

Other stuff is just me trying to make the current Gatsby source plugin more usable by all the packages. I am waiting on a pull request getting approved by the Gatsby team (however, that has hit a few road blocks due to potentially breaking changes, so it's back with me for small edits).

@mgmolisani
Copy link
Contributor

Not exactly what this issue was about since this isn't Gatsby specific but npm i -S @contentful/rich-text-react-renderer is ready for primetime (yay!) and can hopefully help everyone work around the no JSX in the Gatsby config and node files and avoid using dangerouslySetInnerHTML.

@janosh
Copy link

janosh commented Feb 11, 2019

That's great! I've been waiting for this! How would I use that in a Gatsby project? Do I still include gatsby-transformer-contentful-richtext in my gatsby-config.js. But then how do I connect that plugin to my documentToReactComponents(document, options)?

@mgmolisani
Copy link
Contributor

@janosh

How would I use that in a Gatsby project?

documentToReactComponents(document, options) where document is the a parsed rich text document which can be accessed by key.key where key is the API entry title of your rich text node. Consult the docs for more info about options (very similar to the HTML implementation).

Here is an example of a helper component for consuming this document (I may make a PR to add this to the package).

import PropTypes from 'prop-types';
import { documentToReactComponents } from '@contentful/rich-text-react-renderer';

const RichText = ({ document, options }) => {
  return documentToReactComponents(document, options);
};

RichText.propTypes = {
  document: PropTypes.object,
  options: PropTypes.object,
};

export default RichText;

Do I still include gatsby-transformer-contentful-richtext in my gatsby-config.js.

No. Since JSX is not transpiled in the node and config files (without some serious work arounds anyway), you have to handle this outside the plugin. You will only need the source plugin to get the stringified AST as described above. There is PR for this based on this gist to add the JSON AST in the source plugin so parsing will not be necessary in the future.

@janosh
Copy link

janosh commented Feb 11, 2019

@mgmolisani Thanks for the quick response! That's basically what I'm doing (I think, since didn't get the key.key part). Still I'm getting TypeError: Cannot read property 'map' of undefined in nodeListToReactComponents.

error

richBody looks like this.

richbody

Here's a stripped-down version of my code. Can you see what I'm doing wrong?

import React from "react"
import { graphql } from "gatsby"

import { BLOCKS } from "@contentful/rich-text-types"
import { documentToReactComponents } from "@contentful/rich-text-react-renderer"

import PageTitle from "../components/PageTitle"

const Person = ({ name }) => <h2>{name}</h2>

const options = {
  renderNode: {
    [BLOCKS.EMBEDDED_ENTRY]: node => {
      const { name } = node.data.target.fields
      return <Person name={name.de} />
    },
  },
}

const PageTemplate = ({ data }) => {
  const { title, richBody } = data.page
  richBody && console.log(documentToReactComponents(richBody, options))
  return (
      <PageTitle>
        <h1>{title}</h1>
      </PageTitle>
  )
}

export default PageTemplate

export const query = graphql`
  query($slug: String!) {
    page: contentfulPage(slug: { eq: $slug }) {
      title
      richBody {
        nodeType
        content {
          nodeType
          data {
            target {
              fields {
                name {
                  de
                }
              }
            }
          }
        }
      }
    }
  }
`

@mgmolisani
Copy link
Contributor

mgmolisani commented Feb 11, 2019

@janosh Partial trees will likely cause errors with default renderers. In your case, the single paragraph tag is trying to access content for children but you have not added its content in your graphql query. To resolve this, use:

In your graphql query

...
richBody {
    richBody
}
...

And this in the document arg:

JSON.parse(richBody.richBody) // This is the `key.key` part I was talking about

Soon, key.key will be deprecated in favor of key.json as an actually object instead of a string which you will no longer need to parse before putting in the document argument so watch out for that.

@janosh
Copy link

janosh commented Feb 12, 2019

Awesome! Thanks so much for your help! Never would have thought of JSON.parse(richBody.richBody) but it's working perfectly! Looking forward to key.json.

@janosh
Copy link

janosh commented Feb 12, 2019

@mgmolisani Do you know if there is or will be a way to transform data on rich text embedded entries? E.g. format date strings with date(formatString: "MMM D, YYYY"), pass long text fields into gatsby-transformer-remark or grab gatsby-image's fixed or fluid objects?

The date formatting I assume can already be done by including it into the GraphQL query explicitly rather than just requesting richBody.richBody (although a site-wide option to return all dates in a certain format would be cool, even if that's something Gatsby would have to implement first and which Contentful rich text could then honor) but the other two seem more difficult.

@mgmolisani
Copy link
Contributor

@janosh I will try to answer these here but you may want to ask some of these in the community Slack channel as they are deviating from this thread. I also do not know Contentful's plans for the future.

Do you know if there is or will be a way to transform data on rich text embedded entries?

This renderer exposes the raw node as the first argument in the callback for exactly this reason. On the entry type of your choosing, pass the date as a prop and handle that prop in your React component however you like (i.e. run it through formatter and display it as children).

// Embed.jsx
const Embed = ({date}) => {
  return <h6>{format(date)}</h6>;
};

// Wherever you put your renderer
const options = {
  renderNode: {
    [BLOCKS.EMBEDDED_ENTRY]: node => {
      return <Embed date={node.path.to.date} />
    },
  },
}

pass long text fields into gatsby-transformer-remark

Look into remark-react. Markdown is a standard unlike the rich text AST Contentful produces. There are pretty sophisticated libs out there to handle markdown files.

grab gatsby-image's fixed or fluid objects?

I think the source package already does this? Maybe not in the embeds though? If it doesn't, you could probably handle it with a second query on the ID. Not sure, haven't run into a time where I wanted Images as part of my rich text (although I know others do). I actually prefer making 'slices' (many nested references) and pulling more complex structures like lists and images out of the rich text only giving users control over the inline things like marks and links. Check out this page for more gatsby-image info and support.

site-wide option to return all dates in a certain format would be cool

Sounds painful to implement using SaaS instead of your own backend or middleware. You'd have to know very early on you want this to to implement it in every component (either hardcoded or using context). It may be better when making your queries to handle it there or make a transformer plugin to handle it in the specific locations you know it will occur (after all, you build the queries). I can't imagine your main content is dates though so the refactor shouldn't be too stressful if your components are sufficiently separating their duties.

@MaralS
Copy link

MaralS commented Mar 6, 2019

Current working code if anyone else is working on this:

https://gist.github.com/sarahbethfederman/04613d376188f71a1995228f33c38328

When can I found a test repository to see how the code is impletemented please ?
I'm quite lost in all the explanations.
Have a great day

@martzoukos
Copy link
Contributor

Hello there,

I am Contentful's product manager working on our Rich Text editor and we recently released an improvement in the gatsby-source-contentful plugin where it doesn't return a flattened HTML string anymore but the Rich Text AST as a JSON object, with all the embedded entries and assets resolved. Then you can use our rendering libraries in order to render the content into HTML/React/Plain text.

Thank you and let me know if that works.

@janosh
Copy link

janosh commented Mar 14, 2019

@martzoukos Very cool! I'm excited to start using Contentful Rich Text in production. Not sure I understand you correctly, though. Does

richBody {
  json
}

give me any additional data that wasn't resolved before in

richBody {
  richBody
}

@mgmolisani
Copy link
Contributor

@janosh
They use the same internals. No difference other than one spits out a string and has a variable name and the other returns an object (which won’t need to be parsed first like the string version).

@sbezludny
Copy link
Collaborator

Will be closing this issue due to the deprecation of gatsby-transformer-contentful-richtext package in favour of gatsby-source-contentful (see #46 (comment) and reasons for deprecation for more details).

Feel free to ask any further questions in our community slack channel or send them me directly.

@skarges3
Copy link

@mgmolisani would you know why I can only query the second one of those examples, and when I query richBody { json } it fails?

@mgmolisani
Copy link
Contributor

@skarges3 are you using the most up to date source package?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests