diff --git a/src/index.js b/src/index.js index 653bba22..43a0b4ff 100644 --- a/src/index.js +++ b/src/index.js @@ -2,9 +2,8 @@ * Base Markdown component * @author Mient-jan Stelling */ -import React, { Component } from 'react'; +import { useMemo } from 'react'; import PropTypes from 'prop-types'; -import { View } from 'react-native'; import parser from './lib/parser'; import applyStyle from './lib/util/applyStyle'; import getUniqueID from './lib/util/getUniqueID'; @@ -38,178 +37,111 @@ export { }; /** - * react-native-markdown-renderer + * + * @param children + * @return {string} */ -export default class Markdown extends Component { - /** - * Definition of the prop types - */ - static propTypes = { - children: PropTypes.node.isRequired, - renderer: PropTypes.oneOfType([PropTypes.func, PropTypes.instanceOf(AstRenderer)]), - rules: (props, propName, componentName) => { - let invalidProps = []; - const prop = props[propName]; +const getCopyFromChildren = children => { + return children instanceof Array ? children.join('') : children; +}; - if (!prop) { - return; - } +const getRenderer = (renderer, rules, style) => { + if (renderer && rules) { + console.warn( + 'react-native-markdown-renderer you are using renderer and rules at the same time. This is not possible, props.rules is ignored' + ); + } - if (typeof prop === 'object') { - invalidProps = Object.keys(prop).filter(key => typeof prop[key] !== 'function'); - } + if (renderer && style) { + console.warn( + 'react-native-markdown-renderer you are using renderer and style at the same time. This is not possible, props.style is ignored' + ); + } - if (typeof prop !== 'object') { - return new Error( - `Invalid prop \`${propName}\` supplied to \`${componentName}\`. Must be of shape {[index:string]:function} ` - ); - } else if (invalidProps.length > 0) { - return new Error( - `Invalid prop \`${propName}\` supplied to \`${componentName}\`. These ` + - `props are not of type function \`${invalidProps.join(', ')}\` ` - ); + // these checks are here to prevent extra overhead. + if (renderer) { + if (!(typeof renderer === 'function') || renderer instanceof AstRenderer) { + return renderer; + } else { + throw new Error('Provided renderer is not compatible with function or AstRenderer. please change'); + } + } else { + return new AstRenderer( + { + ...renderRules, + ...(rules || {}), + }, + { + ...styles, + ...style, } - }, - markdownit: PropTypes.instanceOf(MarkdownIt), - plugins: PropTypes.arrayOf(PropTypes.instanceOf(PluginContainer)), - style: PropTypes.any, - }; + ); + } +}; - /** - * Default Props - */ - static defaultProps = { - renderer: null, - rules: null, - plugins: [], - style: null, - markdownit: MarkdownIt({ - typographer: true, - }), - }; +const getMarkdownParser = (markdownit, plugins) => { + let md = markdownit; + if (plugins && plugins.length > 0) { + plugins.forEach(plugin => { + md = md.use.apply(md, plugin.toArray()); + }); + } - copy = ''; - renderer = null; - markdownParser = null; + return md; +}; - /** - * Only when the copy changes will the markdown render again. - * @param nextProps - * @param nextState - * @return {boolean} - */ - shouldComponentUpdate(nextProps, nextState) { - const copy = this.getCopyFromChildren(nextProps.children); +/** + * react-native-markdown-renderer + */ +const Markdown = ({ + children, + renderer = null, + rules = null, + plugins = [], + style = null, + markdownit = MarkdownIt({ + typographer: true, + }), +}) => { + const momoizedRenderer = useMemo(() => getRenderer(renderer, rules, style), [renderer, rules, style]); + const markdownParser = useMemo(() => getMarkdownParser(markdownit, plugins), [markdownit, plugins]); + + const copy = (this.copy = getCopyFromChildren(children)); + return parser(copy, momoizedRenderer.render, markdownParser); +}; - if (copy !== this.copy) { - this.copy = copy; - return true; +/** + * Definition of the prop types + */ +Markdown.propTypes = { + children: PropTypes.node.isRequired, + renderer: PropTypes.oneOfType([PropTypes.func, PropTypes.instanceOf(AstRenderer)]), + rules: (props, propName, componentName) => { + let invalidProps = []; + const prop = props[propName]; + + if (!prop) { + return; } - if ( - nextProps.renderer !== this.props.renderer || - nextProps.style !== this.props.style || - nextProps.plugins !== this.props.plugins || - nextProps.rules !== this.props.rules || - nextProps.markdownit !== this.props.markdownit - ) { - return true; + if (typeof prop === 'object') { + invalidProps = Object.keys(prop).filter(key => typeof prop[key] !== 'function'); } - return false; - } - - /** - * - * @param props - */ - updateSettings(props = this.props) { - const { renderer, rules, style, plugins, markdownit } = props; - - if (renderer && rules) { - console.warn( - 'react-native-markdown-renderer you are using renderer and rules at the same time. This is not possible, props.rules is ignored' + if (typeof prop !== 'object') { + return new Error( + `Invalid prop \`${propName}\` supplied to \`${componentName}\`. Must be of shape {[index:string]:function} ` ); - } - - if (renderer && style) { - console.warn( - 'react-native-markdown-renderer you are using renderer and style at the same time. This is not possible, props.style is ignored' + } else if (invalidProps.length > 0) { + return new Error( + `Invalid prop \`${propName}\` supplied to \`${componentName}\`. These ` + + `props are not of type function \`${invalidProps.join(', ')}\` ` ); } + }, + markdownit: PropTypes.instanceOf(MarkdownIt), + plugins: PropTypes.arrayOf(PropTypes.instanceOf(PluginContainer)), + style: PropTypes.any, +}; - // these checks are here to prevent extra overhead. - if (renderer) { - if (typeof renderer === 'function') { - if (!this.renderer || this.renderer.render !== renderer) { - this.renderer = { - render: renderer, - }; - } - } else if (renderer instanceof AstRenderer) { - if (this.renderer !== renderer) { - this.renderer = renderer; - } - } else { - throw new Error('Provided renderer is not compatible with function or AstRenderer. please change'); - } - } else { - if (!this.renderer || this.props.renderer || this.props.rules !== rules || this.props.style !== style) { - this.renderer = new AstRenderer( - { - ...renderRules, - ...(rules || {}), - }, - { - ...styles, - ...style, - } - ); - } - } - - if (!this.markdownParser || this.props.markdownit !== markdownit || plugins !== this.props.plugins) { - let md = markdownit; - if (plugins && plugins.length > 0) { - plugins.forEach(plugin => { - md = md.use.apply(md, plugin.toArray()); - }); - } - - this.markdownParser = md; - } - } - - /** - * - */ - componentWillMount() { - this.updateSettings(this.props); - } - - /** - * - * @param nextProps - */ - componentWillReceiveProps(nextProps) { - this.updateSettings(nextProps); - } - - /** - * - * @param children - * @return {string} - */ - getCopyFromChildren(children = this.props.children) { - return children instanceof Array ? children.join('') : children; - } - - /** - * - * @return {View} - */ - render() { - const copy = (this.copy = this.getCopyFromChildren()); - return parser(copy, this.renderer.render, this.markdownParser); - } -} +export default Markdown;