From cb26666bb815e9061e7f05f0a3f5bb7a66a24ad3 Mon Sep 17 00:00:00 2001 From: Talysson de Oliveira Cassiano Date: Mon, 19 Jun 2017 01:40:28 -0300 Subject: [PATCH] Add error handling (#9) --- .eslintrc | 2 ++ README.md | 28 +++++++++++++++--- dist/react-katex.js | 34 ++++++++++++++++------ dist/react-katex.min.js | 2 +- example/index.html | 15 ++++++++++ package.json | 2 +- src/createMathComponent.js | 33 +++++++++++++++------ test/sharedExamples.js | 59 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 151 insertions(+), 24 deletions(-) diff --git a/.eslintrc b/.eslintrc index 905ff8a..fb10e25 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,6 +14,8 @@ "semi": ["error", "always"], "react/display-name": 0, "react/jsx-filename-extension": 0, + "react/jsx-indent": [2, 2], + "react/jsx-indent-props": [2, 2], "react/no-danger": 0, "react/no-set-state": 0, "react/no-unused-prop-types": 0 diff --git a/README.md b/README.md index 007fc4c..191d5e4 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,12 @@ Display math in the middle of the text. ```jsx var InlineMath = ReactKaTeX.InlineMath; - React.render(, + ReactDOM.render(, document.getElementById('math')); // or - React.render(\int_0^\infty x^2 dx, + ReactDOM.render(\int_0^\infty x^2 dx, document.getElementById('math')); ``` @@ -57,15 +57,35 @@ Display math in a separated block, with larger font and symbols. ```jsx var BlockMath = ReactKaTeX.BlockMath; - React.render(, + ReactDOM.render(, document.getElementById('math')); // or - React.render(\int_0^\infty x^2 dx, + ReactDOM.render(\int_0^\infty x^2 dx, document.getElementById('math')); ``` It will be rendered like this: ![Block math](example/block.png) + + +### Error handling + +It's possible to handle parse errors using the prop `renderError`. This prop must be a function that receives the error object and returns what should be rendered when parsing fails: + +```jsx +var BlockMath = ReactKaTeX.BlockMath; + +ReactDOM.render( + { + return Fail: {error.name} + }} + />, + document.getElementById('math')); + +// The code above will render 'Fail: ParseError' because it's the value returned from `renderError`. +``` diff --git a/dist/react-katex.js b/dist/react-katex.js index 9df8b9e..bb92d33 100644 --- a/dist/react-katex.js +++ b/dist/react-katex.js @@ -159,24 +159,31 @@ return /******/ (function(modules) { // webpackBootstrap _this.usedProp = props.math ? 'math' : 'children'; - _this.state = { - html: _this.generateHtml(props) - }; + _this.state = _this.createNewState(null, props); return _this; } _createClass(MathComponent, [{ key: 'componentWillReceiveProps', - value: function componentWillReceiveProps(nextProps) { - this.setState({ - html: this.generateHtml(nextProps) - }); + value: function componentWillReceiveProps() { + this.setState(this.createNewState); } }, { key: 'shouldComponentUpdate', value: function shouldComponentUpdate(nextProps) { return nextProps[this.usedProp] !== this.props[this.usedProp]; } + }, { + key: 'createNewState', + value: function createNewState(prevState, props) { + try { + var html = this.generateHtml(props); + + return { html: html, error: undefined }; + } catch (error) { + return { error: error, html: undefined }; + } + } }, { key: 'generateHtml', value: function generateHtml(props) { @@ -185,7 +192,15 @@ return /******/ (function(modules) { // webpackBootstrap }, { key: 'render', value: function render() { - return _react2.default.createElement(Component, { html: this.state.html }); + if (this.state.html) { + return _react2.default.createElement(Component, { html: this.state.html }); + } + + if (this.props.renderError) { + return this.props.renderError(this.state.error); + } + + throw this.state.error; } }]); @@ -194,7 +209,8 @@ return /******/ (function(modules) { // webpackBootstrap MathComponent.propTypes = { children: _react2.default.PropTypes.string, - math: _react2.default.PropTypes.string + math: _react2.default.PropTypes.string, + renderError: _react2.default.PropTypes.func }; return MathComponent; diff --git a/dist/react-katex.min.js b/dist/react-katex.min.js index 5a3b7c3..0a68c69 100644 --- a/dist/react-katex.min.js +++ b/dist/react-katex.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("katex")):"function"==typeof define&&define.amd?define("ReactKaTeX",["react","katex"],t):"object"==typeof exports?exports.ReactKaTeX=t(require("react"),require("katex")):e.ReactKaTeX=t(e.React,e.katex)}(this,function(e,t){return function(e){function t(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var r={};return t.m=e,t.c=r,t.p="",t(0)}([function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0});var o=r(1);Object.defineProperty(t,"InlineMath",{enumerable:!0,get:function(){return n(o)["default"]}});var u=r(5);Object.defineProperty(t,"BlockMath",{enumerable:!0,get:function(){return n(u)["default"]}})},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0});var o=r(2),u=n(o),a=r(3),i=n(a),l=function(e){var t=e.html;return u["default"].createElement("span",{dangerouslySetInnerHTML:{__html:t}})};l.propTypes={html:u["default"].PropTypes.string.isRequired},t["default"]=(0,i["default"])(l,{displayMode:!1})},function(t,r){t.exports=e},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e,t){for(var r=0;rWith children +
+

With error handling

+ +
+ Trying to render "\int_{":
+
+
+ @@ -70,6 +78,13 @@

With children

ReactDOM.render(React.createElement(BlockMath, null, '\\int_0^\\infty x^2 dx'), document.getElementById('children_block')); + + ReactDOM.render(React.createElement(BlockMath, { + math: '\\int_{', + renderError: function(error) { + return React.createElement('b', null, 'You tried to render but got ' + error.name); + } + }), document.getElementById('error_handling')); diff --git a/package.json b/package.json index 79a6bca..680d36c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-katex", - "version": "1.1.0", + "version": "1.2.0", "description": "Display math in TeX with KaTeX and ReactJS", "keywords": [ "react", diff --git a/src/createMathComponent.js b/src/createMathComponent.js index 3fadfcb..fee9f6d 100644 --- a/src/createMathComponent.js +++ b/src/createMathComponent.js @@ -8,21 +8,27 @@ const createMathComponent = (Component, { displayMode }) => { this.usedProp = props.math ? 'math' : 'children'; - this.state = { - html: this.generateHtml(props) - }; + this.state = this.createNewState(null, props); } - componentWillReceiveProps(nextProps) { - this.setState({ - html: this.generateHtml(nextProps) - }); + componentWillReceiveProps() { + this.setState(this.createNewState); } shouldComponentUpdate(nextProps) { return nextProps[this.usedProp] !== this.props[this.usedProp]; } + createNewState(prevState, props) { + try { + const html = this.generateHtml(props); + + return { html, error: undefined }; + } catch(error) { + return { error, html: undefined }; + } + } + generateHtml(props) { return KaTeX.renderToString( props[this.usedProp], @@ -31,13 +37,22 @@ const createMathComponent = (Component, { displayMode }) => { } render() { - return ; + if(this.state.html) { + return ; + } + + if(this.props.renderError) { + return this.props.renderError(this.state.error); + } + + throw this.state.error; } } MathComponent.propTypes = { children: React.PropTypes.string, - math: React.PropTypes.string + math: React.PropTypes.string, + renderError: React.PropTypes.func }; return MathComponent; diff --git a/test/sharedExamples.js b/test/sharedExamples.js index c43b34d..8d6516e 100644 --- a/test/sharedExamples.js +++ b/test/sharedExamples.js @@ -6,6 +6,10 @@ import KaTeX from 'katex'; export default (Component, { wrapperTag, displayMode }) => { const sumFormula = '\\sum_0^\\infty'; const integralFormula = '\\int_{-infty}^\\infty'; + const brokenFormula = '\\int_{'; + const renderError = (error) => ( + {`${error.name}: Invalid formula ${brokenFormula}`} + ); context('when passing the formula as props', () => { it('renders correctly', () => { @@ -51,4 +55,59 @@ export default (Component, { wrapperTag, displayMode }) => { }); }); + + describe('error handling', () => { + it('renders the returned value from `renderError` prop when formula is invalid', () => { + const math = shallow( + + ); + + expect(math.html()).to.equal( + 'ParseError: Invalid formula \\int_{' + ); + }); + + it('updates when passing from invalid to valid formula', () => { + const math = shallow( + + ); + + math.setProps({ + math: integralFormula + }); + + expect(math.html()).to.equal( + `<${wrapperTag}>${ KaTeX.renderToString(integralFormula, { displayMode }) }` + ); + }); + + it('updates when passing from valid to invalid formula', () => { + const math = shallow( + + ); + + math.setProps({ + math: brokenFormula + }); + + expect(math.html()).to.equal( + 'ParseError: Invalid formula \\int_{' + ); + }); + + it('blows when no `renderError` prop is passed', () => { + expect(() => { + shallow(); + }).to.throw('KaTeX parse error'); + }); + }); };