Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Using contentful with gatsby and react
import escape from 'escape-html';
import React from 'react';
import {
Document,
Mark,
Text,
BLOCKS,
MARKS,
INLINES,
Block,
Inline,
helpers,
} from '@contentful/rich-text-types';
let NODE_KEY_I = 0;
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.EMBEDDED_ASSET]: (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, next) => (
<a href={node.data.target.sys.id}>{next(node.content)}</a>
),
[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 = {}) {
NODE_KEY_I = 0;
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),
key: NODE_KEY_I++
};
}, nodeValue);
}
return nodeValue;
} else {
console.log(node)
const nextNode = nodes => nodeListToJSX(nodes, { renderMark, renderNode });
if (!node.nodeType || !renderNode[node.nodeType]) {
return <span />;
}
return {
...renderNode[node.nodeType](node, nextNode),
key: NODE_KEY_I++
};
}
}
import React from 'react';
import { Link as GatsbyLink } from 'gatsby';
import * as deepmerge from 'deepmerge';
import { INLINES, BLOCKS, MARKS } from '@contentful/rich-text-types';
import { documentToJSX } from './../scripts/documentToJSX';
import { getEntry } from './../scripts/getContentfulEntry';
const Link = ({ children, to, activeClassName, ...other }) => {
const internal = /^\/(?!\/)/.test(to);
// To do: add test for public path (for absolute links that are still internal)
// Use Gatsby Link for internal links, and <a> for others
if (internal) {
return (
<GatsbyLink to={to} activeClassName={activeClassName} {...other}>
{children}
</GatsbyLink>
)
}
return (
<a href={to} {...other}>
{children}
</a>
)
}
const renderComponent = (node, next) => {
const {
data: {
target: {
fields,
sys: {
contentType: {
sys: { id },
},
},
},
},
} = node;
// Add custom components here
switch (id) {
case 'column':
return <div {...fields} />;
default:
return <span />;
}
};
const renderAsset = (node, next) => {
const {
data: {
target: {
fields: {
file,
title
}
},
},
} = node;
if (file['en-US'].contentType.includes('image')) {
return <img src={file['en-US'].url} alt={title} />
}
return <span />
};
// Override-able via options prop
const options = {
renderNode: {
[BLOCKS.PARAGRAPH]: (node, next) => (
<p className="spectrum-Body3">{next(node.content)}</p>
),
[BLOCKS.EMBEDDED_ASSET]: (node, next) => renderAsset(node, next),
[BLOCKS.EMBEDDED_ENTRY]: (node, next) => renderComponent(node, next),
[INLINES.ENTRY_HYPERLINK]: (node, next) => (
<GatsbyLink
to={node.data.target.fields ? node.data.target.fields.slug['en-US'] : '/404'}>
{next(node.content)}
</GatsbyLink>
),
[INLINES.HYPERLINK]: (node, next) => {
return (
<Link className="spectrum-Link" to={node.data.uri}>
{next(node.content)}
</Link>
);
}
},
};
class RichTextRenderer extends React.Component {
getOptions = () => {
if (this.props.options) {
return deepmerge(options, this.props.options);
}
return options;
};
render() {
const { content } = this.props;
const JSONContent = JSON.parse(content);
const renderOpts = this.getOptions();
const JSX = documentToJSX(JSONContent, renderOpts);
return JSX;
}
}
export default RichTextRenderer;
@sarahbethfederman

This comment has been minimized.

Copy link
Owner Author

@sarahbethfederman sarahbethfederman commented Jan 9, 2019

To run with correctly resolved entries, I'm using build:watch. Does not currently work with gatsby develop due to embedded entries bug.

  "scripts": {
    "build": "npx gatsby build",
    "build:watch": "nodemon --watch src --exec 'npm run deploy'",
    "serve": "npx gatsby serve",
    "deploy": "npm run build && npm run serve",
  }

To use:

In your graphQL:

[FIELDNAME] {
  childContentfulRichText {
    internal {
      content
    }
  }
}

In your JSX:

// override a component for this instance
const options = {
  renderNode: {
    [BLOCKS.UL_LIST]: (node, next) => <CustomComponent>{next(node.content)}</CustomComponent>,
  },
};
<RichTextRenderer options={options} content={[FIELDNAME].childContentfulRichText.internal.content} />
@MaralS

This comment has been minimized.

Copy link

@MaralS MaralS commented Mar 5, 2019

To run with correctly resolved entries, I'm using build:watch. Does not currently work with gatsby develop due to embedded entries bug.

  "scripts": {
    "build": "npx gatsby build",
    "build:watch": "nodemon --watch src --exec 'npm run deploy'",
    "serve": "npx gatsby serve",
    "deploy": "npm run build && npm run serve",
  }

To use:

In your graphQL:

[FIELDNAME] {
  childContentfulRichText {
    internal {
      content
    }
  }
}

In your JSX:

// override a component for this instance
const options = {
  renderNode: {
    [BLOCKS.UL_LIST]: (node, next) => <CustomComponent>{next(node.content)}</CustomComponent>,
  },
};
<RichTextRenderer options={options} content={[FIELDNAME].childContentfulRichText.internal.content} />

Hi @sarah
I was wondering if you had a repository where you implemented your scripts. I'm a newbie with contentful, and I have some troubles to display my entries and assets
Thanks in advance
Maral

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.