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

Reworks the Markdown component to a functional component while also ... #125

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 92 additions & 160 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;