react: Different way to do React components

This is a different approach to React components suggested by @arturi.
Instead of the components adding and removing plugins when mounting and
unmounting, plugins are configured at the start, and the components act
as slots for the plugins to actually mount in. Because all plugins are
still being configured up front here, we don't have to change how the
provider `target:` options work, they can install themselves into eg.
the Dashboard plugin immediately.

The core of this approach is the `UppyWrapper` component, which takes an
Uppy instance and a plugin ID, and mounts the plugin inside itself. The
other components only provide a default plugin ID.

The one change required in Uppy itself for this to work is to the
`mount()` functions: now, `mount()` configures `this.target` on the
plugins. If plugins do not have a `target:` option, they do not mount on
install. Some DOM code also had to be moved into the `mount()` function
for the DragDrop component instead of staying in `install()`, but I
think it makes sense.

(Still have to work out how to make the `DashboardModal` component's
`onRequestClose` option work.)
This commit is contained in:
Renée Kooi 2017-09-11 11:57:29 +02:00
parent 92cf9ab8f9
commit d330c26813
No known key found for this signature in database
GPG key ID: 30516CF2A8E63718
10 changed files with 108 additions and 134 deletions

View file

@ -419,8 +419,10 @@ module.exports = class DashboardUI extends Plugin {
}})
const target = this.opts.target
const plugin = this
this.target = this.mount(target, plugin)
if (target) {
this.mount(target, this)
}
if (!this.opts.disableStatusBar) {
this.core.use(StatusBar, {

View file

@ -36,7 +36,7 @@ module.exports = class DragDrop extends Plugin {
// Default options
const defaultOpts = {
target: '.UppyDragDrop',
target: null,
getMetaFromForm: true,
locale: defaultLocale
}
@ -146,7 +146,13 @@ module.exports = class DragDrop extends Plugin {
install () {
const target = this.opts.target
const plugin = this
this.target = this.mount(target, plugin)
if (target) {
this.mount(target, plugin)
}
}
mount (...args) {
super.mount(...args)
const dndContainer = this.target.querySelector('.UppyDragDrop-container')
this.removeDragDropListener = dragDrop(dndContainer, (files) => {
@ -155,8 +161,13 @@ module.exports = class DragDrop extends Plugin {
})
}
uninstall () {
unmount (...args) {
this.removeDragDropListener()
super.unmount(...args)
}
uninstall () {
this.unmount()
}
}

View file

@ -13,7 +13,6 @@ const getFormData = require('get-form-data')
* @return {array | string} files or success/fail message
*/
module.exports = class Plugin {
constructor (core, opts) {
this.core = core
this.opts = opts || {}
@ -72,34 +71,39 @@ module.exports = class Plugin {
this.el = plugin.render(this.core.state)
targetElement.appendChild(this.el)
this.target = targetElement
return targetElement
}
const Target = target
// Find the target plugin instance.
let targetPlugin
this.core.iteratePlugins((plugin) => {
if (plugin instanceof Target) {
targetPlugin = plugin
return false
}
})
if (typeof target === 'function') {
const Target = target
// Find the target plugin instance.
let targetPlugin
this.core.iteratePlugins((plugin) => {
if (plugin instanceof Target) {
targetPlugin = plugin
return false
}
})
if (targetPlugin) {
const targetPluginName = targetPlugin.id
this.core.log(`Installing ${callerPluginName} to ${targetPluginName}`)
return targetPlugin.addTarget(plugin)
if (targetPlugin) {
const targetPluginName = targetPlugin.id
this.core.log(`Installing ${callerPluginName} to ${targetPluginName}`)
this.target = targetPlugin
return targetPlugin.addTarget(plugin)
}
}
this.core.log(`Not installing ${callerPluginName}`)
return null
throw new Error(`Invalid target option given to ${callerPluginName}`)
}
unmount () {
if (this.el && this.el.parentNode) {
this.el.parentNode.removeChild(this.el)
}
this.target = null
}
install () {

View file

@ -37,7 +37,9 @@ module.exports = class ProgressBar extends Plugin {
install () {
const target = this.opts.target
const plugin = this
this.target = this.mount(target, plugin)
if (target) {
this.mount(target, plugin)
}
}
uninstall () {

View file

@ -132,7 +132,9 @@ module.exports = class StatusBarUI extends Plugin {
install () {
const target = this.opts.target
const plugin = this
this.target = this.mount(target, plugin)
if (target) {
this.mount(target, plugin)
}
}
uninstall () {

View file

@ -1,7 +1,7 @@
const React = require('react')
const PropTypes = require('prop-types')
const UppyCore = require('../core/Core')
const DashboardPlugin = require('../plugins/Dashboard')
const UppyWrapper = require('./Wrapper')
const h = React.createElement
@ -10,46 +10,14 @@ const h = React.createElement
* renders the Dashboard inline, so you can put it anywhere you want.
*/
class Dashboard extends React.Component {
componentDidMount () {
const uppy = this.props.uppy
const options = Object.assign({}, this.props, {
target: this.container,
inline: true
})
delete options.uppy
uppy.use(DashboardPlugin, options)
this.plugin = uppy.getPlugin('DashboardUI')
}
componentWillUnmount () {
const uppy = this.props.uppy
uppy.removePlugin(this.plugin)
}
render () {
return h('div', {
ref: (container) => {
this.container = container
}
})
}
}
const Dashboard = (props) =>
h(UppyWrapper, props)
Dashboard.propTypes = {
uppy: PropTypes.instanceOf(UppyCore).isRequired,
maxWidth: PropTypes.number,
maxHeight: PropTypes.number,
semiTransparent: PropTypes.bool,
defaultTabIcon: PropTypes.node,
showProgressDetails: PropTypes.bool,
locale: PropTypes.object
uppy: PropTypes.instanceOf(UppyCore).isRequired
}
Dashboard.defaultProps = {
locale: {}
plugin: 'DashboardUI'
}
module.exports = Dashboard

View file

@ -1,7 +1,7 @@
const React = require('react')
const PropTypes = require('prop-types')
const UppyCore = require('../core/Core')
const DashboardPlugin = require('../plugins/Dashboard')
const UppyWrapper = require('./Wrapper')
const h = React.createElement
@ -13,14 +13,8 @@ const h = React.createElement
class DashboardModal extends React.Component {
componentDidMount () {
const uppy = this.props.uppy
const options = Object.assign({}, this.props, {
target: this.container,
onRequestHideModal: this.props.onRequestClose
})
delete options.uppy
uppy.use(DashboardPlugin, options)
this.plugin = uppy.getPlugin('DashboardUI')
this.plugin = uppy.getPlugin(this.props.plugin)
if (this.props.open) {
this.plugin.showModal()
}
@ -34,34 +28,19 @@ class DashboardModal extends React.Component {
}
}
componentWillUnmount () {
const uppy = this.props.uppy
uppy.removePlugin(this.plugin)
}
render () {
return h('div', {
ref: (container) => {
this.container = container
}
})
return h(UppyWrapper, this.props)
}
}
DashboardModal.propTypes = {
uppy: PropTypes.instanceOf(UppyCore).isRequired,
maxWidth: PropTypes.number,
maxHeight: PropTypes.number,
semiTransparent: PropTypes.bool,
defaultTabIcon: PropTypes.node,
showProgressDetails: PropTypes.bool,
onRequestClose: PropTypes.func,
locale: PropTypes.object
open: PropTypes.bool,
onRequestClose: PropTypes.func
}
DashboardModal.defaultProps = {
locale: {}
plugin: 'DashboardUI'
}
module.exports = DashboardModal

View file

@ -1,7 +1,7 @@
const React = require('react')
const PropTypes = require('prop-types')
const UppyCore = require('../core/Core')
const DragDropPlugin = require('../plugins/DragDrop')
const UppyCore = require('../core')
const UppyWrapper = require('./Wrapper')
const h = React.createElement
@ -10,32 +10,14 @@ const h = React.createElement
* uploaded.
*/
class DragDrop extends React.Component {
componentDidMount () {
const uppy = this.props.uppy
const options = Object.assign({}, this.props, {
target: this.container
})
delete options.uppy
uppy.use(DragDropPlugin, options)
}
render () {
return h('div', {
ref: (container) => {
this.container = container
}
})
}
}
const DragDrop = (props) =>
h(UppyWrapper, props)
DragDrop.propTypes = {
uppy: PropTypes.instanceOf(UppyCore).isRequired,
locale: PropTypes.object
uppy: PropTypes.instanceOf(UppyCore).isRequired
}
DragDrop.defaultProps = {
locale: {}
plugin: 'DragDrop'
}
module.exports = DragDrop

View file

@ -1,7 +1,7 @@
const React = require('react')
const PropTypes = require('prop-types')
const UppyCore = require('../core/Core')
const ProgressBarPlugin = require('../plugins/ProgressBar')
const UppyWrapper = require('./Wrapper')
const h = React.createElement
@ -10,32 +10,14 @@ const h = React.createElement
* uploaded.
*/
class ProgressBar extends React.Component {
componentDidMount () {
const uppy = this.props.uppy
const options = Object.assign({}, this.props, {
target: this.container
})
delete options.uppy
uppy.use(ProgressBarPlugin, options)
}
render () {
return h('div', {
ref: (container) => {
this.container = container
}
})
}
}
const ProgressBar = (props) =>
h(UppyWrapper, props)
ProgressBar.propTypes = {
uppy: PropTypes.instanceOf(UppyCore).isRequired,
locale: PropTypes.object
uppy: PropTypes.instanceOf(UppyCore).isRequired
}
ProgressBar.defaultProps = {
locale: {}
plugin: 'ProgressBar'
}
module.exports = ProgressBar

42
src/uppy-react/Wrapper.js Normal file
View file

@ -0,0 +1,42 @@
const React = require('react')
const PropTypes = require('prop-types')
const UppyCore = require('../core')
const h = React.createElement
class UppyWrapper extends React.Component {
constructor (props) {
super(props)
this.refContainer = this.refContainer.bind(this)
}
componentDidMount () {
const plugin = this.props.uppy
.getPlugin(this.props.plugin)
plugin.mount(this.container, plugin)
}
componentWillUnmount () {
const plugin = this.props.uppy
.getPlugin(this.props.plugin)
plugin.unmount()
}
refContainer (container) {
this.container = container
}
render () {
return h('div', { ref: this.refContainer })
}
}
UppyWrapper.propTypes = {
uppy: PropTypes.instanceOf(UppyCore).isRequired,
plugin: PropTypes.string.isRequired
}
module.exports = UppyWrapper