Reduce updates caused by typing (#604)

* prevent index page from updating

* implement shouldComponentUpdate for ExportMenu

* replace componentDidUpdate with updateState in Editor

* replace shallowCompare with React.memo

* add flex-basis to prevent jank
main
Sean 6 years ago committed by Michael Fix
parent 0aa1d7404f
commit 5206dc4ef1

@ -6,7 +6,6 @@ import { DragDropContext } from 'react-dnd'
import domtoimage from 'dom-to-image' import domtoimage from 'dom-to-image'
import ReadFileDropContainer, { DATA_URL, TEXT } from 'dropperx' import ReadFileDropContainer, { DATA_URL, TEXT } from 'dropperx'
import Spinner from 'react-spinner' import Spinner from 'react-spinner'
import shallowCompare from 'react-addons-shallow-compare'
// Ours // Ours
import Button from './Button' import Button from './Button'
@ -102,7 +101,7 @@ class Editor extends React.Component {
newState.language = unescapeHtml(newState.language) newState.language = unescapeHtml(newState.language)
} }
this.setState(newState) this.updateState(newState)
window.addEventListener('offline', this.setOffline) window.addEventListener('offline', this.setOffline)
window.addEventListener('online', this.setOnline) window.addEventListener('online', this.setOnline)
@ -113,18 +112,13 @@ class Editor extends React.Component {
window.removeEventListener('online', this.setOnline) window.removeEventListener('online', this.setOnline)
} }
componentDidUpdate(prevProps, prevState) { updateState = updates => this.setState(updates, () => this.props.onUpdate(this.state))
// this.props ensures props are not compared, only state
if (shallowCompare(this, this.props, prevState)) {
this.props.onUpdate(this.state)
}
}
updateCode = code => this.setState({ code }) updateCode = code => this.updateState({ code })
updateAspectRatio = aspectRatio => this.setState({ aspectRatio }) updateAspectRatio = aspectRatio => this.updateState({ aspectRatio })
updateTitleBar = titleBar => this.setState({ titleBar }) updateTitleBar = titleBar => this.updateState({ titleBar })
setOffline = () => this.setState({ online: false }) setOffline = () => this.updateState({ online: false })
setOnline = () => this.setState({ online: true }) setOnline = () => this.updateState({ online: true })
async getCarbonImage( async getCarbonImage(
{ {
@ -208,9 +202,9 @@ class Editor extends React.Component {
} }
updateSetting(key, value) { updateSetting(key, value) {
this.setState({ [key]: value }) this.updateState({ [key]: value })
if (Object.prototype.hasOwnProperty.call(DEFAULT_SETTINGS, key)) { if (Object.prototype.hasOwnProperty.call(DEFAULT_SETTINGS, key)) {
this.setState({ preset: null }) this.updateState({ preset: null })
} }
} }
@ -231,29 +225,29 @@ class Editor extends React.Component {
} }
resetDefaultSettings() { resetDefaultSettings() {
this.setState({ ...DEFAULT_SETTINGS, preset: DEFAULT_PRESET_ID }) this.updateState({ ...DEFAULT_SETTINGS, preset: DEFAULT_PRESET_ID })
this.props.onReset() this.props.onReset()
} }
upload() { upload() {
this.setState({ uploading: true }) this.updateState({ uploading: true })
this.getCarbonImage({ format: 'png' }) this.getCarbonImage({ format: 'png' })
.then(this.props.api.tweet.bind(null, this.state.code || DEFAULT_CODE)) .then(this.props.api.tweet.bind(null, this.state.code || DEFAULT_CODE))
// eslint-disable-next-line // eslint-disable-next-line
.catch(console.error) .catch(console.error)
.then(() => this.setState({ uploading: false })) .then(() => this.updateState({ uploading: false }))
} }
onDrop([file]) { onDrop([file]) {
if (isImage(file)) { if (isImage(file)) {
this.setState({ this.updateState({
backgroundImage: file.content, backgroundImage: file.content,
backgroundImageSelection: null, backgroundImageSelection: null,
backgroundMode: 'image', backgroundMode: 'image',
preset: null preset: null
}) })
} else { } else {
this.setState({ code: file.content, language: 'auto' }) this.updateState({ code: file.content, language: 'auto' })
} }
} }
@ -267,13 +261,13 @@ class Editor extends React.Component {
updateBackground({ photographer, ...changes } = {}) { updateBackground({ photographer, ...changes } = {}) {
if (photographer) { if (photographer) {
this.setState(({ code = DEFAULT_CODE }) => ({ this.updateState(({ code = DEFAULT_CODE }) => ({
...changes, ...changes,
code: code + `\n\n// Photo by ${photographer.name} on Unsplash`, code: code + `\n\n// Photo by ${photographer.name} on Unsplash`,
preset: null preset: null
})) }))
} else { } else {
this.setState({ ...changes, preset: null }) this.updateState({ ...changes, preset: null })
} }
} }
@ -284,7 +278,7 @@ class Editor extends React.Component {
// create toast here in the future // create toast here in the future
}) })
applyPreset = ({ id: preset, ...settings }) => this.setState({ preset, ...settings }) applyPreset = ({ id: preset, ...settings }) => this.updateState({ preset, ...settings })
render() { render() {
const { const {

@ -17,6 +17,46 @@ const toIFrame = url =>
</iframe> </iframe>
` `
const CopyEmbed = withRouter(
React.memo(
({ router: { asPath } }) => (
<React.Fragment>
<CopyButton text={toIFrame(asPath)}>
{({ copied }) => (
<button className="copy-button">{copied ? 'Copied!' : 'Copy Embed'}</button>
)}
</CopyButton>
<style jsx>
{`
.copy-button {
display: flex;
flex: 1;
flex-basis: 68px;
justify-content: center;
align-items: center;
padding: 12px 16px;
font-size: 12px;
white-space: nowrap;
user-select: none;
cursor: pointer;
outline: none;
border: none;
background: inherit;
color: ${COLORS.PURPLE};
border-right: 1px solid ${COLORS.PURPLE};
}
.copy-button:hover {
opacity: 1;
}
`}
</style>
</React.Fragment>
),
(prevProps, nextProps) => prevProps.router.asPath === nextProps.router.asPath
)
)
class ExportMenu extends React.PureComponent { class ExportMenu extends React.PureComponent {
state = { state = {
isVisible: false isVisible: false
@ -33,7 +73,7 @@ class ExportMenu extends React.PureComponent {
handleExport = format => () => this.props.export(format) handleExport = format => () => this.props.export(format)
render() { render() {
const { exportSize, filename, router } = this.props const { exportSize, filename } = this.props
const { isVisible } = this.state const { isVisible } = this.state
return ( return (
@ -77,11 +117,7 @@ class ExportMenu extends React.PureComponent {
<button className="open-button" onClick={this.handleExport('open')}> <button className="open-button" onClick={this.handleExport('open')}>
Open Open
</button> </button>
<CopyButton text={toIFrame(router.asPath)}> <CopyEmbed />
{({ copied }) => (
<button className="copy-button">{copied ? 'Copied!' : 'Copy Embed'}</button>
)}
</CopyButton>
<div className="save-container"> <div className="save-container">
<span>Save as</span> <span>Save as</span>
<div> <div>
@ -187,7 +223,6 @@ class ExportMenu extends React.PureComponent {
opacity: 1; opacity: 1;
} }
.copy-button,
.open-button, .open-button,
.save-container { .save-container {
display: flex; display: flex;
@ -197,11 +232,6 @@ class ExportMenu extends React.PureComponent {
padding: 12px 16px; padding: 12px 16px;
} }
.copy-button {
white-space: nowrap;
}
.copy-button,
.open-button { .open-button {
border-right: 1px solid ${COLORS.PURPLE}; border-right: 1px solid ${COLORS.PURPLE};
} }
@ -229,4 +259,4 @@ class ExportMenu extends React.PureComponent {
} }
} }
export default withRouter(enhanceWithClickOutside(ExportMenu)) export default enhanceWithClickOutside(ExportMenu)

@ -10,6 +10,8 @@ import { updateQueryString } from '../lib/routing'
import { saveSettings, clearSettings, omit } from '../lib/util' import { saveSettings, clearSettings, omit } from '../lib/util'
class Index extends React.Component { class Index extends React.Component {
shouldComponentUpdate = () => false
onEditorUpdate = state => { onEditorUpdate = state => {
updateQueryString(this.props.router, state) updateQueryString(this.props.router, state)
saveSettings( saveSettings(

Loading…
Cancel
Save