parrisneeds.coffee code, music, drinks

React and Modals

You know what I miss? The ability to launch a dialog, alert or prompt and have it block execution of the current function. I miss the ability to just write var answer = window.prompt('What is your favorite pizza?'); and get the data I need immediately. For the past few years, we as a JS community have probably created a few thousand implementations for modals and the only thing we can agree on is that they all leave something to be desired.

Today, I stumbled upon a new solution, I think, a better solution. What if, getting your data back from a modal was this simple?

let { title, desc } = await createBlockingModal(MyModal);

Personally, I think it is ideal. What we all want is to create modals faster. Hooking a modal into global redux or local react state is a bit tedious. So why not create something a little less stateful?

Soooo where do we start? First off, let's create something that generates a component:

async function createBlockingModal(Component) {
    const modalEl = document.createElement('div');
    document.querySelector('body').append(modalEl);

    return new Promise((resolve, reject) => {
        ReactDOM.render(
            <Component
                onOk={(data) => {
                    ReactDOM.unmountComponentAtNode(modalEl);
                    modalEl.remove();
                    resolve(data);
                }}
                onCancel={(data) => {
                    ReactDOM.unmountComponentAtNode(modalEl);
                    modalEl.remove();
                    reject(data);
                }}
            />,
            modalEl
        );
    });
}

The above code defines how our modal is created, rendered onto the page, subsequently closed, and how the data is returned to the calling function. The promise returned here allows us to later await on the promise being resolved or rejected. Which means we can do something like this:

async function handleClick() {
    try {
        let { title, desc } = await createBlockingModal(MyModal);
    } catch (e) {
        console.log('The user canceled');
    }
}

So, what does the modal look like? Well that's the beauty, your modal can look like anything. Here's a really simple example:

const MyModal = props => (
    <div className="modal-overlay">
        <div className="modal">
            <form onSubmit={(e) => {
                e.preventDefault();
                const title = e.target.querySelector('[name=title]');
                const desc = e.target.querySelector('[name=desc]');
                props.onOk({
                    title: title.value,
                    desc: desc.value,
                });
            }}>
                <input type="text" name="title" />
                <input type="text" name="desc" />
                <input type="submit" value="Save" />
            </form>
        </div>
    </div>
);

Of course you could get fancier and split the modal and the form apart. My main point is that we can get the nice looking API with very little risk, in a really clean/reusable form factor.

Soapbox Time

When I first started using React, I was amazed at how fast I could crank out features. There was a period of time where we as a community realized that things were getting a bit messy so we started managing our state using systems like Redux. We all love Redux, it is amazing, but I think we all get a bit carried away with turning everything into global state. We've forgotten that we are trying to optimize for code cleanliness and more importantly dev speed. Creating systems like the one above let's us get back to a place where writing APIs for our fellow developers is as important as getting the job done.

JS - Vanilla JS in 2016 - Maybe the future is closer than we thought

This story starts with a recent job hunt. One of the companies I applied for asked me to create an interface that searches Twitch. Twitch, for those of you who don't know, is a site where people can stream their activities, mostly around gaming.

The prompt at a high level looked something like this:

  1. One week time limit
  2. Take your time, make it perfect, show us how you'd build a system
  3. No libraries
  4. Must use JSONP

GitHub: https://github.com/parris/twitchstreamsearch/

So, what do you do when people say "one week", "make it perfect", and "no libraries"... Well, if you're anything like me you'll basically make all your own libraries and spend an entire week doing it.

From the very get-go I started thinking about writing my own little mini-react and mini-redux. The next problem I knew I'd face was around module loading or cleanly splitting up my files. Step one for me though was actually to start-off with a jasmine like test runner so I can experiment and safely modify my code.

What followed from then on, was me learning that vanilla, plain-ol-js in 2016 is insanely powerful. Right now Chrome and FF support everything from

template literals

/src/components/Card.js#L23-L24

`${stream.game} - ${stream.viewers} viewers`

to arrow functions

/src/components/Header.js#L13-L18

(e) => {
    e.preventDefault();
    let queryInputEl = document.querySelector('.js-query-input');
    this.props.onSearch(queryInputEl.value);
}

to symbol property keys

/src/reducers/streams.js#L8-L12

const transformers = {
    [actionTypes.search]: function(state, action) {
        return [];
    },
    [actionTypes.searchComplete]: function(state, action) {

to classes

/src/components/Header.js#L9-L11

class Header extends Component {
    events() { ... }
    render() { ... }
}

to promises

/src/utils/request.js#L12

let requestPromise = new Promise(function(resolve, reject) { ... }

to block scoping, const/let, trailing commas, the fetch API, and more.

There were only 2 things missing from my typical Babel/Webpack world.

First off, I was missing a module loader and an easy way to split my files up. In this excercise, I relied on AMD to fill this gap. Functionally, AMD works similarly to the ES6 module proposal. It appears that ES6 modules will indeed be async in their final form. In addition, with the advancements around HTTP2 there should be no need for a separate build step, since HTTP2 prefers many smaller files over 1 large built file.

Modules in my excercise were defined like this:

define(function(require) {
    'use strict';

    const build = require('/src/utils/componentBuilder.js');
    const Component = require('/src/components/Component.js');
    const { Button, Div } = require('/src/components/BasicComponents.js');
    const i18n = require('/src/utils/i18n.js');

    return {};
});

Between "define" and "require" in the above example, the system figures out how to load the tree of dependencies in browser.

Second, Babel also helps us when we are writing React components. In this excercise, I created a component builder that mimics React's composibility. React of course could be used without sans JSX. While writing this post I tried to see if web components were in a mature enough state to use, but it seems that even Chrome doesn't really have support for them out of the box.

I've been working in Babel JavaScript land for quite sometime now (maybe the past 2 years). This is the first time it felt like I didn't need Babel/Webpack anymore. We still seem to be missing a few key elements (modules and components), but we don't seem that far off, and as my example proved those limitations can be overcome today. Maybe the future is closer than we thought! Maybe someday we can drop these bulky build steps!

Links:

  1. GitHub: https://github.com/parris/twitchstreamsearch/
  2. Live Demo
  3. Test Runner

Talks - Nodevember - OpenGL/WebGL + Text (Signed Distance Fields)

At the last Nodevember in Nashville I talked about WebGL (and OpenGL) and the ways you can render text with it. You can read about it in detail here.

Check out the full video here:

and my slides:

FlexBox Formulas - the Search and Go

We've seen "search and go" since the dawn of the internet. It still remains fairly ridiculous to get right.

Typically, we'd fix the width of the input or the button and use one of a few techniques to get the size of the other correct. This breaks down when you have a fluid page and translations.

I'll tell you what you want! You want a beautiful, 100% width, sexy, awesome input box next to a beautiful, perfectly translated, shiny button that just screams "click me!" So let's make this happen. We do have the technology (well mostly).

Show me the code:

Why Dorsal.js

Problem 1: You have all these server side pages and lots of JS components that need to get initialized on every page.

Problem 2: You want to stop manually initializing your JS components with the same code over and over again.

Problem 3: Your backend developers don't really know JS, but they occasionally write some HTML.

Yea. Dorsal fixes all of that.

So what is Dorsal? In short, Dorsal allows you to markup your HTML with classes and data attributes and in return automatically initializes components. These components could be written using Backbone, Marionette, plain JS or whatever else!

Example:

Some developer writes some HTML somewhere on the website like so: <input type="checkbox" class="js-d-toggle" data-d-size="large" />

You had previously created a plugin called 'toggle', and it gets loaded onto the page (however you choose to do so).

var dorsal = requre('dorsal'),

      ToggleView = require('./toggle_view');

dorsal.registerPlugin('toggle', {
    create: function(options) {
        return new ToggleView(options.el, options.data.size);
    },
    destroy: function(options) {
        options.instance.remove();
    }
};

Somewhere on your page, after all the HTML has been written, and possibly after DOM ready you can run wire.

dorsal.wire();

Wire scours the DOM for js-d- prefixed classes, matches them with plugins and passes the element and options into the plugin.

Why???

During our recent responsive push at Eventbrite we needed to be able to allow everyone to simply and quickly initialize JS components. We anticipated our build system getting slower from 100 new files that needed to get run through the RequireJS optimizer. We also anticipated that all of these files would contain pretty much the same code. They would have looked something like:

define(function(require) {

    var SelectBox = require('styleguide/js/select'),
          ToggleSwitch = require('styleguide/js/toggle'),
          ResponsiveTable = require('styleguide/js/tables'),
          // ... up to 20 other plugins

    new SelectBox(el1);
    new Selectbox(el2);
    //... 20 other select boxes, 5 other toggle switches, etc

});

Basically all that, on 80+ pages.

Instead we created Dorsal, and internally what we call our standard component library. The component library loads up all of our standard components, which each have Dorsal plugins in them. Dorsal then runs on domReady and everyone is happy.

But WAIT there's more

Lately, we've been using Dorsal for more. We've been using it in the onRender methods in Marionette classes. We've been using it for one off components that we think might fit nicely into our styleguide eventually. With the lack of a pre-rendering service we've been using it on SEO sensitive pages. We basically were able to keep all our existing Backbone and Marionette code and make our workflow feel closer to React or Angular. We felt this was actually the largest missing piece from the Backbone/Marionette community and so we made it open source! Check it out Dorsal on NPM!