From 13cfcca85eecae4b9d0985bd0786f48c8fd439fa Mon Sep 17 00:00:00 2001
From: Jeff Avallone
Date: Sat, 12 Jan 2019 21:47:36 -0500
Subject: [PATCH] React.Context was overkill for this purpose, not using it
Also using Gatsby's built-in location property
---
src/components/App/index.js | 140 +++++++++++++++++--
src/components/AppContext/index.js | 149 ---------------------
src/components/Form/index.js | 31 +++--
src/components/FormActions/index.js | 13 +-
src/components/Render/index.js | 15 ++-
src/pages/__snapshots__/index.test.js.snap | 36 ++++-
src/pages/index.js | 31 ++++-
src/pages/index.test.js | 12 +-
8 files changed, 236 insertions(+), 191 deletions(-)
delete mode 100644 src/components/AppContext/index.js
diff --git a/src/components/App/index.js b/src/components/App/index.js
index b9f7845..9e7c49e 100644
--- a/src/components/App/index.js
+++ b/src/components/App/index.js
@@ -1,11 +1,62 @@
import React from 'react';
+import PropTypes from 'prop-types';
import * as Sentry from '@sentry/browser';
+import URLSearchParams from '@ungap/url-search-params';
-import { AppContextProvider } from 'components/AppContext';
import Form from 'components/Form';
import Loader from 'components/Loader';
import Message from 'components/Message';
+const toUrl = params => new URLSearchParams(params).toString();
+
+const createSvgLink = async ({ svg }) => {
+ try {
+ const type = 'image/svg+xml';
+ const blob = new Blob([svg], { type });
+
+ return {
+ url: URL.createObjectURL(blob),
+ label: 'Download SVG',
+ filename: 'image.svg',
+ type
+ };
+ }
+ catch (e) {
+ console.error(e); // eslint-disable-line no-console
+ }
+};
+
+const createPngLink = async ({ svg, width, height }) => {
+ try {
+ const type = 'image/png';
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ const loader = new Image();
+
+ loader.width = canvas.width = Number(width) * 2;
+ loader.height = canvas.height = Number(height) * 2;
+
+ await new Promise(resolve => {
+ loader.onload = resolve;
+ loader.src = 'data:image/svg+xml,' + encodeURIComponent(svg);
+ });
+
+ context.drawImage(loader, 0, 0, loader.width, loader.height);
+
+ const blob = await new Promise(resolve => canvas.toBlob(resolve, type));
+
+ return {
+ url: URL.createObjectURL(blob),
+ label: 'Download PNG',
+ filename: 'image.png',
+ type
+ };
+ }
+ catch (e) {
+ console.error(e); // eslint-disable-line no-console
+ }
+};
+
class App extends React.PureComponent {
state = {
loading: false,
@@ -13,15 +64,43 @@ class App extends React.PureComponent {
Render: null
}
- handleRender = async ({ syntax, expr }) => {
+ componentDidMount() {
+ if (this.props.expr) {
+ this.handleRender();
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ const { syntax, expr } = this.props;
+
+ if (syntax !== prevProps.syntax || expr !== prevProps.expr) {
+ this.handleRender();
+ }
+ }
+
+ handleSubmit = ({ syntax, expr }) => {
+ if (expr) {
+ document.location.hash = toUrl({ syntax, expr });
+ }
+ }
+
+ handleRender = async () => {
+ const { syntax, expr } = this.props;
+
this.setState({
- syntax,
- expr,
- loading: true,
+ loading: false,
loadingError: null,
Render: null
});
+ if (!expr) {
+ return;
+ }
+
+ this.setState({
+ loading: true
+ });
+
try {
const Render = await import(
/* webpackChunkName: "render-[index]" */
@@ -51,18 +130,51 @@ class App extends React.PureComponent {
handleRetry = event => {
event.preventDefault();
- this.handleRender(this.state);
+ this.handleRender();
+ }
+
+ handleSvgMarkup = async ({ svg, width, height }) => {
+ if (svg !== this.state.svg) {
+ this.setState({
+ svg,
+ svgLink: await createSvgLink({ svg }),
+ pngLink: await createPngLink({ svg, width, height })
+ });
+ }
}
render() {
+ const {
+ syntax,
+ expr,
+ permalinkUrl
+ } = this.props;
const {
loading,
loadingError,
- Render
+ Render,
+ svgLink,
+ pngLink
} = this.state;
- return
-
+ const formProps = {
+ onSubmit: this.handleSubmit,
+ syntax,
+ expr,
+ actions: {
+ permalinkUrl,
+ svgLink,
+ pngLink
+ }
+ };
+ const renderProps = {
+ onRender: this.handleSvgMarkup,
+ syntax,
+ expr
+ };
+
+ return <>
+
{ loading && }
@@ -71,9 +183,15 @@ class App extends React.PureComponent {
Retry
}
- { Render && }
- ;
+ { Render && }
+ >;
}
}
+App.propTypes = {
+ syntax: PropTypes.string,
+ expr: PropTypes.string,
+ permalinkUrl: PropTypes.string
+};
+
export default App;
diff --git a/src/components/AppContext/index.js b/src/components/AppContext/index.js
deleted file mode 100644
index 6743015..0000000
--- a/src/components/AppContext/index.js
+++ /dev/null
@@ -1,149 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import URLSearchParams from '@ungap/url-search-params';
-
-const AppContext = React.createContext();
-
-const toUrl = params => new URLSearchParams(params).toString();
-
-const readURLHash = () => {
- const query = document.location.hash.slice(1);
- const params = new URLSearchParams(query);
-
- if (params.get('syntax')) {
- return {
- syntax: params.get('syntax'),
- expr: params.get('expr')
- };
- } else {
- // Assuming old-style URL
- return {
- syntax: 'js',
- expr: query
- };
- }
-};
-
-const createSvgLink = async ({ svg }) => {
- try {
- const type = 'image/svg+xml';
- const blob = new Blob([svg], { type });
-
- return {
- url: URL.createObjectURL(blob),
- label: 'Download SVG',
- filename: 'image.svg',
- type
- };
- }
- catch (e) {
- console.error(e); // eslint-disable-line no-console
- }
-};
-
-const createPngLink = async ({ svg, width, height }) => {
- try {
- const type = 'image/png';
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- const loader = new Image();
-
- loader.width = canvas.width = Number(width) * 2;
- loader.height = canvas.height = Number(height) * 2;
-
- await new Promise(resolve => {
- loader.onload = resolve;
- loader.src = 'data:image/svg+xml,' + encodeURIComponent(svg);
- });
-
- context.drawImage(loader, 0, 0, loader.width, loader.height);
-
- const blob = await new Promise(resolve => canvas.toBlob(resolve, type));
-
- return {
- url: URL.createObjectURL(blob),
- label: 'Download PNG',
- filename: 'image.png',
- type
- };
- }
- catch (e) {
- console.error(e); // eslint-disable-line no-console
- }
-};
-
-class AppContextProvider extends React.PureComponent {
- state = {}
-
- mutations = {
- setSvgMarkup: async ({ svg, width, height }) => {
- if (svg !== this.state.svg) {
- this.setState({
- svg,
- svgLink: await createSvgLink({ svg }),
- pngLink: await createPngLink({ svg, width, height })
- });
- }
- },
-
- renderExpr: ({ syntax, expr }) => {
- if (expr) {
- document.location.hash = toUrl({ syntax, expr });
- }
- }
- }
-
- componentDidMount() {
- // Gatsby doesn't have document.location, so readURLHash can't be called
- // until here
- this.setState(readURLHash());
-
- window.addEventListener('hashchange', this.handleHashChange);
- this.handleHashChange();
- }
-
- componentWillUnmount() {
- window.removeEventListener('hashchange', this.handleHashChange);
- }
-
- handleHashChange = () => {
- const { expr, syntax } = readURLHash();
-
- if (!expr) {
- return;
- }
-
- this.setState({
- syntax,
- expr,
- permalinkUrl: document.location.toString()
- });
-
- if (this.props.onExpressionChange) {
- this.props.onExpressionChange({ syntax, expr });
- }
- }
-
- render() {
- const { children } = this.props;
- const context = {
- ...this.state,
- ...this.mutations
- };
-
- return
- { children }
- ;
- }
-}
-
-AppContextProvider.propTypes = {
- onExpressionChange: PropTypes.func,
- children: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node
- ]).isRequired
-};
-
-export { AppContextProvider };
-export default AppContext;
diff --git a/src/components/Form/index.js b/src/components/Form/index.js
index c8b03fc..b95950c 100644
--- a/src/components/Form/index.js
+++ b/src/components/Form/index.js
@@ -1,10 +1,10 @@
import React from 'react';
+import PropTypes from 'prop-types';
import ExpandIcon from 'react-feather/dist/icons/chevrons-down';
import style from './style.module.css';
-import AppContext from 'components/AppContext';
import FormActions from 'components/FormActions';
const syntaxList = [
@@ -13,20 +13,9 @@ const syntaxList = [
];
class Form extends React.PureComponent {
- static contextType = AppContext
-
state = {
- expr: this.context.expr,
- syntax: this.context.syntax
- }
-
- componentDidUpdate() {
- if (this.state.expr === undefined && this.state.syntax === undefined) {
- this.setState({
- syntax: this.context.syntax,
- expr: this.context.expr
- });
- }
+ expr: this.props.expr,
+ syntax: this.props.syntax
}
handleSubmit = event => {
@@ -34,7 +23,7 @@ class Form extends React.PureComponent {
const { expr, syntax } = this.state;
- this.context.renderExpr({ expr, syntax });
+ this.props.onSubmit({ expr, syntax });
}
handleKeyPress = event => {
@@ -48,6 +37,9 @@ class Form extends React.PureComponent {
})
render() {
+ const {
+ actions
+ } = this.props;
const { expr, syntax } = this.state;
return
@@ -71,10 +63,17 @@ class Form extends React.PureComponent {
-
+
;
}
}
+Form.propTypes = {
+ expr: PropTypes.string,
+ syntax: PropTypes.string,
+ actions: PropTypes.object,
+ onSubmit: PropTypes.func.isRequired
+};
+
export default Form;
diff --git a/src/components/FormActions/index.js b/src/components/FormActions/index.js
index 33a3319..2616188 100644
--- a/src/components/FormActions/index.js
+++ b/src/components/FormActions/index.js
@@ -1,15 +1,12 @@
import React from 'react';
+import PropTypes from 'prop-types';
import DownloadIcon from 'react-feather/dist/icons/download';
import LinkIcon from 'react-feather/dist/icons/link';
import style from './style.module.css';
-import AppContext from 'components/AppContext';
-
class FormActions extends React.PureComponent {
- static contextType = AppContext
-
downloadLink({ url, filename, type, label }) {
return
@@ -23,7 +20,7 @@ class FormActions extends React.PureComponent {
permalinkUrl,
svgLink,
pngLink
- } = this.context;
+ } = this.props;
return
{ pngLink && this.downloadLink(pngLink) }
@@ -35,4 +32,10 @@ class FormActions extends React.PureComponent {
}
}
+FormActions.propTypes = {
+ permalinkUrl: PropTypes.string,
+ svgLink: PropTypes.object,
+ pngLink: PropTypes.object
+};
+
export default FormActions;
diff --git a/src/components/Render/index.js b/src/components/Render/index.js
index d76f67a..39e5184 100644
--- a/src/components/Render/index.js
+++ b/src/components/Render/index.js
@@ -1,14 +1,11 @@
import React from 'react';
+import PropTypes from 'prop-types';
import PlaceholderIcon from 'react-feather/dist/icons/file-text';
import style from './style.module.css';
-import AppContext from 'components/AppContext';
-
class Render extends React.PureComponent {
- static contextType = AppContext
-
svgContainer = React.createRef()
provideSVGData() {
@@ -17,7 +14,7 @@ class Render extends React.PureComponent {
}
const svg = this.svgContainer.current.querySelector('svg');
- this.context.setSvgMarkup({
+ this.props.onRender({
svg: svg.outerHTML,
width: svg.getAttribute('width'),
height: svg.getAttribute('height')
@@ -33,7 +30,7 @@ class Render extends React.PureComponent {
}
render() {
- const { syntax, expr } = this.context;
+ const { syntax, expr } = this.props;
console.log('Render:', syntax, expr); // eslint-disable-line no-console
@@ -44,4 +41,10 @@ class Render extends React.PureComponent {
}
}
+Render.propTypes = {
+ syntax: PropTypes.string,
+ expr: PropTypes.string,
+ onRender: PropTypes.func.isRequired
+};
+
export default Render;
diff --git a/src/pages/__snapshots__/index.test.js.snap b/src/pages/__snapshots__/index.test.js.snap
index 29e6f63..1efe07c 100644
--- a/src/pages/__snapshots__/index.test.js.snap
+++ b/src/pages/__snapshots__/index.test.js.snap
@@ -22,6 +22,40 @@ exports[`Index Page rendering 1`] = `
-
+
+
+`;
+
+exports[`Index Page rendering with an expression on the URL 1`] = `
+
+
+
+
`;
diff --git a/src/pages/index.js b/src/pages/index.js
index a5d22ec..73b0d7a 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -1,11 +1,34 @@
import React from 'react';
+import PropTypes from 'prop-types';
import { Link } from 'gatsby';
+import URLSearchParams from '@ungap/url-search-params';
import Metadata from 'components/Metadata';
import Message from 'components/Message';
import App from 'components/App';
-export const IndexPage = () => <>
+const readURLHash = location => {
+ const query = location.hash.slice(1);
+ const params = new URLSearchParams(query);
+ const permalinkUrl = query ? location.href : null;
+
+ if (params.get('syntax')) {
+ return {
+ syntax: params.get('syntax'),
+ expr: params.get('expr'),
+ permalinkUrl
+ };
+ } else {
+ // Assuming old-style URL
+ return {
+ syntax: 'js',
+ expr: query,
+ permalinkUrl
+ };
+ }
+};
+
+export const IndexPage = ({ location }) => <>
-
+
>;
+IndexPage.propTypes = {
+ location: PropTypes.object
+};
+
export default IndexPage;
diff --git a/src/pages/index.test.js b/src/pages/index.test.js
index c7e0144..b2e5179 100644
--- a/src/pages/index.test.js
+++ b/src/pages/index.test.js
@@ -6,7 +6,17 @@ import { IndexPage } from 'pages/index';
describe('Index Page', () => {
test('rendering', () => {
const component = shallow(
-
+
+ );
+ expect(component).toMatchSnapshot();
+ });
+
+ test('rendering with an expression on the URL', () => {
+ const component = shallow(
+
);
expect(component).toMatchSnapshot();
});