contentful / rich-text

Libraries for handling and rendering Rich Text 📄
MIT License
549 stars 109 forks source link

Rendering rich text in React Native #102

Open thib92 opened 5 years ago

thib92 commented 5 years ago

Thanks to rich-text-react-render, we can render Contentful rich text inside a React app. Moreover, some defaults are provided for DOM rendering (using tags like <p>, <h1> and so on).

However, it is not possible to render these tags in a React Native app, as React Native uses custom components, not DOM elements (for example <Text> for text).

Fortunately, rich-text-react-renderer makes possible to override the default components and allows to provide a custom renderer for each type of content.

Then, it is up to the user to provide their own React Native renderer.

Do you think it could be a good idea to provide a default renderer for React Native as well?

People might override it to use their own components for easier styling, but this could make it easier to get started using Contentful with React Native.

Khaledgarbaya commented 5 years ago

cc @sbezludny @cribbles

Khaledgarbaya commented 5 years ago

Since the react-renderer is flexible enough I think we can have a package that just exports a renderer option to add the rich-text-react-renderer

Example:

import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { reactNativeRenderer } from '@contentful/rich-text-react-renderer';

documentToReactComponents(document, reactNativeRenderer)
henrymoulton commented 5 years ago

@thib92 So would an <h1/> translate to a <Text style={{ fontSize: CONTENTFUL_DEFAULT_H1_FONT_SIZE }}/> ?

Khaledgarbaya commented 5 years ago

@henrymoulton you can always override any default config

IsaacHardy commented 4 years ago

For those of you who came here looking for a solution, try looking in react-native-render-html.

5ervant commented 4 years ago

Since the react-renderer is flexible enough I think we can have a package that just exports a renderer option to add the rich-text-react-renderer

Example:

import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { reactNativeRenderer } from '@contentful/rich-text-react-renderer';

documentToReactComponents(document, reactNativeRenderer)

@Khaledgarbaya Upon doing this:

documentToReactComponents(item, reactNativeRenderer)
// OR
documentToReactComponents(item.fields, reactNativeRenderer)

I'm getting:

undefined is not an object (evaluating 'nodes.map') - node_modules\@contentful\rich-text-react-renderer\dist\rich-text-react-renderer.es5.js:878:9 in nodeListToReactComponents

With this:

documentToReactComponents(item.fields.body, reactNativeRenderer)
// OR
<Text>{documentToReactComponents(item.fields.body, reactNativeRenderer)}</Text>

I'm getting:

Text strings must be rendered within a component. - node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:4137:14 in - node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:4134:2 in createTextInstance
- node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:15909:12 in completeWork


For those of you who came here looking for a solution, try looking in react-native-render-html.

@IsaacHardy Could you give a sample mock-up? For example how you can render a Contentful entry item.fields.body with react-native-render-html?

By the way, I'm using Expo.

Khaledgarbaya commented 4 years ago

@5ervant you need to pass the content of the rich-text field

5ervant commented 4 years ago

@Khaledgarbaya item.fields.body is the rich text content.

5ervant commented 4 years ago

@Khaledgarbaya Anyway, reactNativeRenderer is undefined.

import { reactNativeRenderer } from '@contentful/rich-text-react-renderer';

console.log('reactNativeRenderer', reactNativeRenderer); // reactNativeRenderer == undefined
Khaledgarbaya commented 4 years ago

Sorry for the confusion here yes it is undefined because it does not exist

I was just giving a suggestion

5ervant commented 4 years ago

Yes, a confusion because this issue is more like a feature request.

My found solution is to use ReactDOMServer and such a react-native-webview library:

import ReactDOMServer from 'react-dom/server';
import { WebView } from 'react-native-webview';
// ...
    <WebView
      originWhitelist={['*']}
      source={{
        html: ReactDOMServer.renderToStaticMarkup(ContentfulRichTextForWeb(item.fields.body))
      }}
    />
jackdewhurst commented 4 years ago

Yes, a confusion because this issue is more like a feature request.

My found solution is to use ReactDOMServer and such a react-native-webview library:

import ReactDOMServer from 'react-dom/server';
import { WebView } from 'react-native-webview';
// ...
    <WebView
      originWhitelist={['*']}
      source={{
        html: ReactDOMServer.renderToStaticMarkup(ContentfulRichTextForWeb(item.fields.body))
      }}
    />

Interesting, where is ContentfulRichTextForWeb generated?

neil-gebbie-smarterley commented 4 years ago

For anyone looking for a solution, you can use this as a starting point. Your use case will be different based on where your using it, but for most people it's enough to get started. I've edited the code inside the github comment box so if there is errors or formatting issues sorry about that.

const contentfulToReactnative = {
    renderMark: {
      [MARKS.UNDERLINE]: (text) => {
        return text;
      },
      [MARKS.BOLD]: (text) => {
        return <Text>{text}</Text>;
      },
      [MARKS.ITALIC]: (text) => {
        return <Text>{text}</Text>;
      },
      [MARKS.CODE]: (text) => {
        return <Text>{text}</Text>;
      },
    },
    renderNode: {
      [INLINES.HYPERLINK]: (node) => {
        return null;
      },
      [BLOCKS.EMBEDDED_ENTRY]: (node) => {
        return null;
      },
      [BLOCKS.PARAGRAPH]: (_node, children) => {
        return <Text>{children}</Text>;
      },
      [BLOCKS.EMBEDDED_ASSET]: (node) => {
        return null;
      },
      [BLOCKS.HEADING_1]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.HEADING_2]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.HEADING_3]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.HEADING_4]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.HEADING_5]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.HEADING_6]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.UL_LIST]: (_node, children) => {
        return (
          <View>
            {children.map((child, i) => {
              return child;
            })}
          </View>
        );
      },
      [BLOCKS.OL_LIST]: (_node, children) => {
        return children.map((child, i) => {
          return child;
        });
      },
      [BLOCKS.LIST_ITEM]: (_node, child) => {
        return <View>{child}</View>;
      },
      [BLOCKS.QUOTE]: (_node, child) => {
        return <Text>{child}</Text>;
      },
      [BLOCKS.HR]: (_node, child) => {
        return <Text>{child}</Text>;
      },
    },
};

And then you can do this:

import { documentToReactComponents } from '@contentful/rich-text-react-renderer'
documentToReactComponents(objFromContentful, contentfulToReactnative)
thomashagstrom commented 3 years ago

Like a miracle this

Since the react-renderer is flexible enough I think we can have a package that just exports a renderer option to add the rich-text-react-renderer

Example:

import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { reactNativeRenderer } from '@contentful/rich-text-react-renderer';

documentToReactComponents(document, reactNativeRenderer)

Like a miracle I actually got this working by:

A) transforming response using https://www.contentful.com/developers/docs/javascript/tutorials/rendering-contentful-rich-text-with-javascript/

import {documentToHtmlString} from '@contentful/rich-text-html-renderer';
import {Document} from '@contentful/rich-text-types';

import getContentfulEntries, {
  ContentfulLocale,
} from '../../lib/contentful/getContentfulEntries';
import {IFood, IFoodContentful} from './IFood';

export const GetFoodItems = async (
  locale?: string,
): Promise<IFood[] | undefined> => {
  try {
    const response = await getContentfulEntries<IFoodContentful>({
      entryType: 'food',
      locale: locale || ContentfulLocale,
    });

    return response.items.map((i) => {
      const html = documentToHtmlString(i.fields.description as Document);
      console.log('FOOD as HTML', html);

      const item: IFood = {
        ...i.fields,
        id: i.sys.id,
        category: i.fields.category.sys.id,
        description: html,
      };
      return item;
    });
  } catch (error) {
    console.warn(error.message);
    return undefined;
  }
};

B) Rendering HTML using https://github.com/archriss/react-native-render-html

import {StatusBar, useWindowDimensions, View} from 'react-native';
import HTML from 'react-native-render-html';
...
export const MenuScreen: React.FC = () => {
  const menuContext = React.useContext(MenuContext);
  const contentWidth = useWindowDimensions().width;

  return (
  <HTML
 source={{html: f.description}}
contentWidth={contentWidth}
 />);
smadan commented 3 years ago

Any updates on this? Doesn't seem like there's support for RN, and the best solution is to render HTML and use react-native-render-html. Any plans on adding first party support for this?

neil-gebbie-smarterley commented 3 years ago

@smadan - Have you tried my solution? I've found it works really well, it's not as flexible as a web based solution, but it's still suitable.

smadan commented 3 years ago

I ended up using @thomashagstrom's solution.

christophsaile commented 3 years ago

@neil-gebbie-smarterley thanks for your help :), works fine for me

neil-gebbie-smarterley commented 3 years ago

@christophsaile No problems. Glad I could help.

avillarubia commented 2 years ago

For anyone looking for a solution, you can use this as a starting point. Your use case will be different based on where your using it, but for most people it's enough to get started. I've edited the code inside the github comment box so if there is errors or formatting issues sorry about that.

const contentfulToReactnative = {
    renderMark: {
      [MARKS.UNDERLINE]: (text) => {
        return text;
      },
      [MARKS.BOLD]: (text) => {
        return <Text>{text}</Text>;
      },
      [MARKS.ITALIC]: (text) => {
        return <Text>{text}</Text>;
      },
      [MARKS.CODE]: (text) => {
        return <Text>{text}</Text>;
      },
    },
    renderNode: {
      [INLINES.HYPERLINK]: (node) => {
        return null;
      },
      [BLOCKS.EMBEDDED_ENTRY]: (node) => {
        return null;
      },
      [BLOCKS.PARAGRAPH]: (_node, children) => {
        return <Text>{children}</Text>;
      },
      [BLOCKS.EMBEDDED_ASSET]: (node) => {
        return null;
      },
      [BLOCKS.HEADING_1]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.HEADING_2]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.HEADING_3]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.HEADING_4]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.HEADING_5]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.HEADING_6]: (_node, children) => <Text>{children}</Text>,
      [BLOCKS.UL_LIST]: (_node, children) => {
        return (
          <View>
            {children.map((child, i) => {
              return child;
            })}
          </View>
        );
      },
      [BLOCKS.OL_LIST]: (_node, children) => {
        return children.map((child, i) => {
          return child;
        });
      },
      [BLOCKS.LIST_ITEM]: (_node, child) => {
        return <View>{child}</View>;
      },
      [BLOCKS.QUOTE]: (_node, child) => {
        return <Text>{child}</Text>;
      },
      [BLOCKS.HR]: (_node, child) => {
        return <Text>{child}</Text>;
      },
    },
};

And then you can do this:

import { documentToReactComponents } from '@contentful/rich-text-react-renderer'
documentToReactComponents(objFromContentful, contentfulToReactnative)

not working for me, I tried this documentToReactComponents(content?.fields?.html, contentfulToReactnative)

neil-gebbie-smarterley commented 2 years ago

And when you console.log(content?.fields?.html) what is it showing? What content type is the field?

neil-gebbie-smarterley commented 2 years ago

@avillarubia ^^^

avillarubia commented 2 years ago

@neil-gebbie-smarterley thank you for your response, this is the sample data, of content

https://codesandbox.io/s/contentful-content-k75jk6?file=/src/data.json

fields has no type as shown in the image below image

html is document in nodeType image