JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://raw.githubusercontent.com/lhorie/mithril.js/components/mithril.js"></script>

CSS

.uploader {border:1px dashed #888;height:100px;}

JavaScript

var Uploader = {
	upload: function(options) {
		var formData = new FormData
		for (var key in options.data) {
			for (var i = 0; i < options.data[key].length; i++) {
				formData.append(key, options.data[key][i])
			}
		}

		//simply pass the FormData object intact to the underlying XMLHttpRequest, instead of JSON.stringify'ing it
		options.serialize = function(value) {return value}
		options.data = formData

		return m.request(options)
	},
	serialize: function(files) {
		var promises = files.map(function(file) {
			var deferred = m.deferred()

			var reader = new FileReader
			reader.readAsDataURL()
			reader.onloadend = function(e) {
				deferred.resolve(e.result)
			}
			reader.onerror = deferred.reject
			return deferred.promise
		})
		return m.sync(promises)
	},
	controller: function(args) {
		this.noop = function(e) {
			e.preventDefault()
		}
		this.update = function(e) {
			e.preventDefault()
			if (typeof args.onchange == "function") {
				args.onchange([].slice.call((e.dataTransfer || e.target).files))
			}
		}
	},
	view: function(ctrl, args) {
		return m(".uploader", {ondragover: ctrl.noop, ondrop: ctrl.update})
	}
}

//usage demo 1: standalone multipart/form-data upload when files are dropped into the component
var Demo1 = {
	controller: function() {
		return {
			upload: function(files) {
				Uploader.upload({method: "POST", url: "/echo/json/", data: {files: files}}).then(function() {
					alert("uploaded!")
				})
			}
		}
	},
	view: function(ctrl) {
		return [
			m("h1", "Uploader demo"),
            m("p", "Drag and drop a file below. An alert box will appear when the upload finishes"),
			m.component(Uploader, {onchange: ctrl.upload})
		]
	}
}

m.mount(document.body, Demo1)