Skip to content

Commit

Permalink
Refactor: use officially supported React context API (#87)
Browse files Browse the repository at this point in the history
* Refactor: replace usage of legacy context API to officially supported React context API

- Refactor context API usage
- Update tests as shallow render does not simulate the actual usage of the new context API
- Drop support of older React versions - package.json peerDependencies is updated

* major bump as we only support react > 16.3
  • Loading branch information
malcolm-kee authored Nov 9, 2021
1 parent 626f973 commit ae62ee3
Show file tree
Hide file tree
Showing 15 changed files with 284 additions and 158 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-mobiledoc-editor",
"version": "0.11.1",
"version": "0.12.0",
"description": "A Mobiledoc editor for React apps",
"repository": "joshfrench/react-mobiledoc-editor",
"homepage": "https://github.com/joshfrench/react-mobiledoc-editor",
Expand Down Expand Up @@ -32,8 +32,8 @@
},
"peerDependencies": {
"mobiledoc-kit": "^0.12.2 || ^0.13.0",
"react": "^0.14.7 || ^15.0.0 || ^16.0.0 || ^17.0.0",
"react-dom": "^0.14.7 || ^15.0.0 || ^16.0.0 || ^17.0.0"
"react": "^16.3.0 || ^17.0.0",
"react-dom": "^16.3.0 || ^17.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.3",
Expand Down
57 changes: 29 additions & 28 deletions src/components/AttributeSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,41 @@ import PropTypes from 'prop-types';
import React from 'react';
import titleCase from '../utils/titleCase';
import getActiveAttribute from '../utils/getActiveAttribute';
import { ReactMobileDocContext } from "./Context";

const AttributeSelect = (
{ values = [], defaultValue = values[0], attribute, ...props },
{ editor, activeSectionAttributes = []}
) => {
const activeAttribute = getActiveAttribute(activeSectionAttributes, attribute, defaultValue);
return (
<ReactMobileDocContext.Consumer>
{({ editor, activeSectionAttributes = []}) => {
const activeAttribute = getActiveAttribute(activeSectionAttributes, attribute, defaultValue);

const onChange = event => {
const { value } = event.target;
if (value === defaultValue) {
editor.removeAttribute(attribute);
} else {
editor.setAttribute(attribute, value);
}
};
const onChange = event => {
const { value } = event.target;
if (value === defaultValue) {
editor.removeAttribute(attribute);
} else {
editor.setAttribute(attribute, value);
}
};

return (
<select value={activeAttribute} onChange={onChange} {...props}>
{activeAttribute === '' && (
<option value={''} key={''}>
</option>
)}
{values.map(v => (
<option value={v} key={v}>
{titleCase(v)}
</option>
))}
</select>
return (
<select value={activeAttribute} onChange={onChange} {...props}>
{activeAttribute === '' && (
<option value={''} key={''}>
</option>
)}
{values.map(v => (
<option value={v} key={v}>
{titleCase(v)}
</option>
))}
</select>
);
}}
</ReactMobileDocContext.Consumer>
);
};

Expand All @@ -39,9 +45,4 @@ AttributeSelect.propTypes = {
values: PropTypes.arrayOf(PropTypes.string).isRequired
};

AttributeSelect.contextTypes = {
editor: PropTypes.object,
activeSectionAttributes: PropTypes.array
};

export default AttributeSelect;
22 changes: 12 additions & 10 deletions src/components/Container.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import * as Mobiledoc from 'mobiledoc-kit';
import { LATEST_MOBILEDOC_VERSION, EMPTY_MOBILEDOC } from '../utils/mobiledoc';
import { ReactMobileDocContext } from "./Context";

class Container extends React.Component {
static childContextTypes = {
Expand Down Expand Up @@ -54,15 +55,6 @@ class Container extends React.Component {
activeSectionAttributes: []
}

getChildContext() {
return {
editor: this.editor,
activeMarkupTags: this.state.activeMarkupTags,
activeSectionTags: this.state.activeSectionTags,
activeSectionAttributes: this.state.activeSectionAttributes
};
}

componentWillUnmount() {
this.editor.destroy();
}
Expand All @@ -73,7 +65,17 @@ class Container extends React.Component {
const { atoms, autofocus, cardProps, cards, children, didCreateEditor, html, mobiledoc, options,
placeholder, serializeVersion, spellcheck, willCreateEditor, ...componentProps } = this.props;
/* eslint-enable no-unused-vars */
return <div {...componentProps}>{children}</div>;
return <div {...componentProps}>
<ReactMobileDocContext.Provider
value={{
editor: this.editor,
activeMarkupTags: this.state.activeMarkupTags,
activeSectionTags: this.state.activeSectionTags,
activeSectionAttributes: this.state.activeSectionAttributes
}}>
{children}
</ReactMobileDocContext.Provider>
</div>;
}

setActiveTags = () => {
Expand Down
4 changes: 4 additions & 0 deletions src/components/Context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import React from 'react';

export const ReactMobileDocContext = React.createContext({});
ReactMobileDocContext.displayName = 'ReactMobileDocContext';
21 changes: 13 additions & 8 deletions src/components/Editor.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import PropTypes from 'prop-types';
import React from 'react';
import { ReactMobileDocContext } from "./Context";

class Editor extends React.Component {
static contextTypes = {
editor: PropTypes.object
}

componentDidMount() {
const { editor } = this.context;
const { editor } = this.props.context;
if (editor) {
editor.render(this.editorEl);
}
}

render() {
return <div {...this.props} ref={r => (this.editorEl = r)} />;
// eslint-disable-next-line no-unused-vars
const { context: _, ...props } = this.props;

return <div {...props} ref={r => (this.editorEl = r)} />;
}
}

export default Editor;
const EditorOuter = React.forwardRef(function EditorOuter(props, ref) {
return <ReactMobileDocContext.Consumer>
{(context) => <Editor {...props} context={context} ref={ref} />}
</ReactMobileDocContext.Consumer>;
});

export default EditorOuter;
38 changes: 19 additions & 19 deletions src/components/LinkButton.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import PropTypes from 'prop-types';
import React from 'react';
import { UI } from 'mobiledoc-kit';
import { ReactMobileDocContext } from "./Context";

const LinkButton = ({ children = "Link", type = "button", handler, className, activeClassName = 'active', ...props }, { editor, activeMarkupTags = []}) => {
const onClick = () => {
if (!editor.hasCursor()) {
return;
}
const LinkButton = ({ children = "Link", type = "button", handler, className, activeClassName = 'active', ...props }) => {
return <ReactMobileDocContext.Consumer>
{({ editor, activeMarkupTags = []}) => {
const onClick = () => {
if (!editor.hasCursor()) {
return;
}

if (editor.hasActiveMarkup('a')) {
editor.toggleMarkup('a');
} else {
UI.toggleLink(editor, handler);
}
};
if (editor.hasActiveMarkup('a')) {
editor.toggleMarkup('a');
} else {
UI.toggleLink(editor, handler);
}
};

className = [className, activeMarkupTags.indexOf('a') > -1 && activeClassName].filter(Boolean).join(' ');
className = [className, activeMarkupTags.indexOf('a') > -1 && activeClassName].filter(Boolean).join(' ');

props = { type, ...props, onClick, className };
return <button { ...props }>{children}</button>;
props = { type, ...props, onClick, className };
return <button { ...props }>{children}</button>;
}}
</ReactMobileDocContext.Consumer>;
};

LinkButton.propTypes = {
children: PropTypes.node
};

LinkButton.contextTypes = {
editor: PropTypes.object,
activeMarkupTags: PropTypes.array
};

export default LinkButton;
20 changes: 10 additions & 10 deletions src/components/MarkupButton.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import PropTypes from 'prop-types';
import React from 'react';
import titleCase from '../utils/titleCase';
import { ReactMobileDocContext } from "./Context";

const MarkupButton = ({ tag = '', type = 'button', children = titleCase(tag), className, activeClassName = 'active', ...props }, { editor, activeMarkupTags = []}) => {
const onClick = () => editor.toggleMarkup(tag);
className = [className, activeMarkupTags.indexOf(tag.toLowerCase()) > -1 && activeClassName].filter(Boolean).join(' ');
props = { type, ...props, onClick, className };
return <button { ...props }>{children}</button>;
const MarkupButton = ({ tag = '', type = 'button', children = titleCase(tag), className, activeClassName = 'active', ...props }) => {
return <ReactMobileDocContext.Consumer>
{({ editor, activeMarkupTags = []}) => {
const onClick = () => editor.toggleMarkup(tag);
className = [className, activeMarkupTags.indexOf(tag.toLowerCase()) > -1 && activeClassName].filter(Boolean).join(' ');
props = { type, ...props, onClick, className };
return <button { ...props }>{children}</button>;
}}
</ReactMobileDocContext.Consumer>;
};

MarkupButton.propTypes = {
tag: PropTypes.string.isRequired,
children: PropTypes.node
};

MarkupButton.contextTypes = {
editor: PropTypes.object,
activeMarkupTags: PropTypes.array
};

export default MarkupButton;
15 changes: 10 additions & 5 deletions src/components/SectionButton.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import PropTypes from 'prop-types';
import React from 'react';
import titleCase from '../utils/titleCase';
import { ReactMobileDocContext } from "./Context";

const SectionButton = ({ tag = '', type = 'button', children = titleCase(tag), className, activeClassName = 'active', ...props }, { editor, activeSectionTags = []}) => {
const onClick = () => editor.toggleSection(tag);
className = [className, activeSectionTags.indexOf(tag.toLowerCase()) > -1 && activeClassName].filter(Boolean).join(' ');
props = { type, ...props, onClick, className };
return <button { ...props }>{children}</button>;
const SectionButton = ({ tag = '', type = 'button', children = titleCase(tag), className, activeClassName = 'active', ...props }) => {
return <ReactMobileDocContext.Consumer>
{({ editor, activeSectionTags = []}) => {
const onClick = () => editor.toggleSection(tag);
className = [className, activeSectionTags.indexOf(tag.toLowerCase()) > -1 && activeClassName].filter(Boolean).join(' ');
props = { type, ...props, onClick, className };
return <button { ...props }>{children}</button>;
}}
</ReactMobileDocContext.Consumer>;
};

SectionButton.propTypes = {
Expand Down
34 changes: 17 additions & 17 deletions src/components/SectionSelect.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import PropTypes from 'prop-types';
import React from 'react';
import { ReactMobileDocContext } from "./Context";

const SectionSelect = ({ tags = [], ...props }, { editor, activeSectionTags = []}) => {
const activeTag = () => tags.find((t) => activeSectionTags.includes(t));
const SectionSelect = ({ tags = [], ...props }) => {
return <ReactMobileDocContext.Consumer>
{({ editor, activeSectionTags = []}) => {
const activeTag = () => tags.find((t) => activeSectionTags.includes(t));

const onChange = (event) => {
const tag = event.target.value || activeTag();
editor.toggleSection(tag);
};
const onChange = (event) => {
const tag = event.target.value || activeTag();
editor.toggleSection(tag);
};

return (
<select value={activeTag() || ""} onChange={onChange} {...props}>
<option value=""></option>
{ tags.map((t) => <option value={t} key={t}>{t.toUpperCase()}</option>) }
</select>
);
return (
<select value={activeTag() || ""} onChange={onChange} {...props}>
<option value=""></option>
{ tags.map((t) => <option value={t} key={t}>{t.toUpperCase()}</option>) }
</select>
);
}}
</ReactMobileDocContext.Consumer>;
};

SectionSelect.propTypes = {
tags: PropTypes.arrayOf(PropTypes.oneOf(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'aside'])).isRequired
};

SectionSelect.contextTypes = {
editor: PropTypes.object,
activeSectionTags: PropTypes.array
};

export default SectionSelect;
Loading

0 comments on commit ae62ee3

Please sign in to comment.