Last active
May 12, 2021 17:56
-
-
Save sarahbethfederman/04613d376188f71a1995228f33c38328 to your computer and use it in GitHub Desktop.
Using contentful with gatsby and react
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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++ | |
}; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
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
To run with correctly resolved entries, I'm using build:watch. Does not currently work with gatsby develop due to embedded entries bug.
To use:
In your graphQL:
In your JSX: