Filed under computers

ReactJS: State, Props and Reusable Components

So here’s a thing.

As I build more components in ReactJS, I’m starting to uncover some interesting use cases. One such case that came up recently involved reusing components — a component that can be used both on its own and as part of another component. Let me explain.

A new feature on ProjectNom involves connecting your user profile to a Twitter account. To make this a more seamless experience, I decided to build a simple React component that surfaces the current state of your Twitter account.

For example, if you haven’t added your Twitter account yet:
Connect Twitter Account Example

If you want to add your Twitter account, then we have to send you to the Twitter website to authenticate, so we display the following:
Redirect to Twitter Example

And finally, if you’ve already connected your Twitter account:
Twitter Connected Example

As you can see, this component revolves heavily around manipulating a button, so I knew that would be the primary element in the render.

But thinking more generally, the second state with the busy indicator was something that jumped out as a component that I could use elsewhere on the site. So, I decided to build a “BusyButton” component which could be used both as part of this Twitter component but also on its own whenever I needed a button to display a “busy” state.

So first, let’s make a BusyButton suitable for our Twitter component. As described in my earlier post, it’s best practice to store state in the outermost parent component. Child components just use their props. So, using that philosophy, we can build our BusyButton component like so:

module.exports = React.createClass({
	render: function() {
		var buttonStyle = "btn-" + this.props.style;
				
		if (this.props.busy)
		{
			var busyLabel = this.props.busyLabel
				? React.DOM.span({style: {paddingLeft: 10}}, this.props.busyLabel)
				: "";

			return React.DOM.button({className: "btn " + buttonStyle, style: {minWidth: 75}, disabled: "disabled"},
				React.DOM.i({className: "fa fa-circle-o-notch fa-spin"}),
				busyLabel
			);
		}
		else
		{
			var icon = this.props.icon
				? React.DOM.i({className: "fa fa-" + this.props.icon, style: {paddingRight: 10}})
				: "";

			var attributes = {className: "btn " + buttonStyle, type: this.props.type, id: this.props.id, onClick: this.props.onClick};
			
			if (this.props.disabled) {
				attributes.disabled = "disabled";
			}

			return React.DOM.button(attributes,
				icon,
				this.props.label
			);
		}
	}
});

As you can see, this component is driven entirely with props. If this is a child component, then that makes sense. If the parent gets rendered again, it will pass new props to the child, and the child’s behavior will change when it renders.

So far so good.

But now we come to our second acceptance criteria. We want this button to have the same behavior on its own: a button that can be marked as busy, and have the same disabled state and indicator icon.

With our current component, we have to set everything with props. That’s fine for the component’s initial render, but if we want to change the component’s behavior after the fact, we have a problem. Props are supposed to be immutable — an initial state and nothing more. (see: Props in getInitialState Is an Anti-Pattern)

Truth be told, we could ignore this advice and use something like componentWillReceiveProps to make the component behave like we expect. The issue I have with this approach is that it ignores the fact we are fundamentally talking about changes in the component’s state. The “active” and “busy” behaviors are two different states that the button can be in. And the button can freely move between those states as necessary.

This was the conundrum: I needed the component to run from props in order to keep state isolated in one spot, but I also needed the component to maintain state so that it could be easily manipulated when used on its own. I never found a satisfactory answer to this problem, so I welcome any suggestions or best practices for others who may have encountered this scenario.

In the meantime, I’ve done something of a compromise:

module.exports = React.createClass({
	getInitialState: function() {
		return {
			busy: this.props.initialBusy,
			disabled: this.props.initialDisabled
		};
	},

	busy: function() {
		this.setState({busy: true});
	},
	
	activate: function() {
		this.setState({busy: false});
	},
	
	disable: function() {
		this.setState({disabled: true});
	},
	
	enable: function() {
		this.setState({disabled: false});
	},

	render: function() {
		var buttonStyle = "btn-" + this.props.style;
				
		if (this.state.busy)
		{
			var busyLabel = this.props.busyLabel
				? React.DOM.span({style: {paddingLeft: 10}}, this.props.busyLabel)
				: "";

			return React.DOM.button({className: "btn " + buttonStyle, style: {minWidth: 75}, disabled: "disabled"},
				React.DOM.i({className: "fa fa-circle-o-notch fa-spin"}),
				busyLabel
			);
		}
		else
		{
			var icon = this.props.icon
				? React.DOM.i({className: "fa fa-" + this.props.icon, style: {paddingRight: 10}})
				: "";

			var attributes = {className: "btn " + buttonStyle, type: this.props.type, id: this.props.id, onClick: this.props.onClick};
			
			if (this.state.disabled) {
				attributes.disabled = "disabled";
			}

			return React.DOM.button(attributes,
				icon,
				this.props.label
			);
		}
	}
});

I ended up using state, since it conceptually made sense for the component. But rather than manipulate state directly, I’ve exposed some custom functions on the component that allow its user (whether it be a parent component or custom JavaScript) to control whether the button is in an active or busy state.

The disadvantage is that you have to use these functions. Passing down props from the parent no longer works, because this component now has state, and state takes precedence over props. Luckily, this is a basic component with only two primary states (active & busy), so it’s easy to manage.

But it’s clear that React doesn’t have good support for this scenario, and I’m not entirely happy with this solution. For a framework that is built entirely around the concept of reusable components, it isn’t very clear how reusability is supposed to work.

Tagged , ,

DRY, Browserify

In my last post, I discussed some of the biggest lessons I learned while building my first component in ReactJS. One of those lessons revolved around Browserify, and how it was the best way to leverage component reuse.

As I moved closer to production, I realized there was one particularly nasty side effect to Browserify. Take the following code for example:

var React = require("react");
var ReactDOM = require("react-dom");

// Custom React Component
var RecipeTabs = require("components/RecipeTabs.js");

function initPage()
{
	ReactDOM.render(
	  React.createElement(RecipeTabs),
	  document.getElementById('recipeTabs')
	);
}

Browserify lets us use CommonJS require syntax even though browsers don’t natively support it. One way it does this is by inlining the entire contents of your require‘d JavaScript into your script. So, in the example above, our 13-line script will suddenly grow to include all of React, all of ReactDOM and all of the code for RecipeTabs.

If we blindly require React in this manner inside every JavaScript file on our site, then we end up with a bunch of duplicate inlined code. Even worse, we are forcing users to download the same code over and over. The browser only sees unique file names and sizes — it isn’t smart enough to realize that inside those files are large chunks of repeated code. What we really want is one file that contains all of our common code, and reuse that file on every page. That way, the browser only downloads the code once; every other page load uses a cached copy.

Thankfully, Browserify natively provides a solution to this issue.

The trick is to create a single file that contains all JavaScript requires used site-wide. This single bundle can then be included on all of your pages.

Browserify gives us an easy way to do this. For example, using gulp:

gulp.task('browserify-global', function() {
	return browserify()
		.require(['react','react-dom'])
		.transform(envify)
		.bundle()
		.pipe(source('exports.js'))
		.pipe(gulp.dest('./js'));
});

This creates an exports.js file which is a bundle of React and ReactDOM. We can now include this on every page of our site — the browser will download it once and then cache it for every subsequent page.

But we still have a problem. Browserify doesn’t know about exports.js when it processes the rest of the site’s JavaScript. It will go ahead and inline React and ReactDOM as usual wherever it’s require‘d.

The second piece to make this work is to tell Browserify to not inline certain require‘d libraries:

gulp.task('browserify-custom', function() {
    return glob('./src/**/*.js', function(err, files) {
        if(!err) {
	        var tasks = files.map(function(entry) {
	            return browserify({ entries: [entry] })
	                .external(['react','react-dom'])
	                .bundle()
	                .pipe(source(entry))
	                .pipe(gulp.dest('./js'));
	        });
		}
	});	
});

Now, whenever Browserify encounters a require for ‘react’ or ‘react-dom’, it won’t inline the script. But, as long as we include the exports.js generated in the previous step, the reference will resolve, and it will be able to execute any React or ReactDOM code.

This isn’t limited to third-party JavaScript libraries either. If you have your own code that is referenced across the entire site, then you can include it:

.require(['react','react-dom', {file: './src/pn.js', expose: 'pn'}])

The expose property specifies the name to use in your require statements. In this case, whenever I need to reference code in pn.js, I can simply require('pn').

In the second step, we can now specify pn as an external library:

.external(['react','react-dom','pn'])
Tagged , , , ,

ReactJS: What I Learned From My First Component

Today marks an important milestone — I’m blogging about a JavaScript library!

Ever since I took a deep-dive JavaScript boot camp a few months ago, I’ve been eager to start a project that would let me hone my newly-acquired skills. To that end, I’ve been refactoring ProjectNom’s (poorly-coded) JavaScript so that it conforms to best practices.

That’s low-hanging fruit though. What I really wanted to sink my teeth into was something like AngularJS — a modern JavaScript framework that takes the language to another level, opening up entirely new ways of rendering a website (e.g. SPA).

But the problem with Angular is that it’s a full MVC framework, and an opinionated one at that. To truly get the most out of it, I’d have to build ProjectNom from scratch – which I had just finished doing for other reasons, and didn’t want to do again so soon.

At work, it has been proposed that we use ReactJS for any front-end development so that components could be shared and re-used across projects. I didn’t know anything about React except the name, so when I had some free time, I decided to research it.

I started to get really excited. Not only did the philosophy of the framework sound right up my alley, but it was also designed in such a way that it could slot easily into an existing project. After a tutorial or two, I made the decision to take it for a spin on ProjectNom. What follows are my experiences and takeaways as I moved from the perfect world of tutorials to a real world use case.

To be clear, this post is not a tutorial. There are many of those already, including on React’s own website. I include a quick introduction to the theory of React in the next section, but after that I will assume you know the basics of how React works so that we can dig a little deeper.

So what is React?

Let’s imagine for a moment that standard HTML elements — div, ul, input, etc — are like LEGO bricks. They each have a function and purpose; but, on their own, they aren’t very interesting or useful.

React gives us a way to define components. A React component is like a LEGO set – it specifies the pieces and assembly instructions to create an interesting, complex object. It’s easy to define your own component, but there are also a large number of ready-made React components available. Either way, once a component is defined, all you have to do is ask React for it, and it’s ready to use on a web page.

Of course, a static collection of HTML elements is only slightly more interesting than a single HTML element. Most web pages are driven by the data that flows through it, and that’s where React’s power is fully realized. Each React component contains a state and this state defines how the component should look or behave. You can specify an initial state when you first ask React for a component, but that state can be modified at any time in response to new data. You could think of it like a LEGO set that specifies exactly which minifigs should go where, but providing the flexibility to move them around later.

If this sounds as interesting to you as it does to me, then I encourage you to check out a tutorial or two. Feel free to come back here once you understand the basics.

Continue reading

Tagged , , , , , , ,

Re-Introducing ProjectNom

ProjectNom Screencap
The original idea for ProjectNom was born about four years ago. At that time, my method of storing recipes involved a pile of printed recipes. As I picked up this pile — a pile with no organization, rhyme or reason — I wondered if there was a better way.

A better way to organize recipes, especially those found online.

A better way to cook those recipes, without having to resort to paper.

A better way to surface those recipes later when it came time to plan a meal.

The options at the time were rather limited. They were all locked to a certain platform, blissfully ignorant of the web. Importing recipes was a pain, and cooking directly from the software wasn’t a primary feature.

I built the first version of ProjectNom quickly, mostly because I wanted to get my own recipe mess under control as soon as possible. What I ended up with was a solid proof of concept, but the accelerated development time forced cracks in the infrastructure. The result was difficult to maintain and difficult to extend — classic hallmarks of such a rushed effort.

This year, I decided to finally give ProjectNom the professional treatment it deserved — or at least, the best treatment a one-person development team could deliver. Specifically, there were a few aspects I wanted to improve over the original “proof of concept”:

  • True responsive design. I used Bootstrap for the original ProjectNom, but a combination of my inexperience with modern front-end development combined with early-version Bootstrap limitations resulted in a lack of finesse, especially on smaller devices.
  • Easier importing. The first version of ProjectNom made importing recipes as easy as selecting text. While that was better than typing it in from scratch, it was still a hassle.
  • Sharing. Part of the fun of cooking is sharing the recipes you find. Having a way to do this in the same place that you store your recipes only makes sense.
  • Technical foundation. The original ProjectNom was difficult to maintain and improve upon.

This work started in January. Eight months later, I’m proud to finally reveal the complete rewrite of ProjectNom.

  • True responsive design. While I’m still not an expert in front-end development, the new ProjectNom is truly responsive across a variety of devices. If it has a relatively modern browser, it’ll look good. Some pages — namely editing a recipe — are more usable on devices with larger screens, but they’ll still render intelligently.
  • Easier importing. The new ProjectNom can quickly import recipes from known sites in one click. At launch, the following sites are supported: Food Network, Epicurious, Serious Eats, Bon Appetit & AllRecipes. The best part is that adding new sites is relatively easy, so please send along suggestions for any site not on this list! In the meantime, the traditional import functionality can be used — just select the text you want and click “import”.
  • Sharing. Invite your friends to join ProjectNom, add them to your list of friends, and then share your recipes! It’s that easy. You can even import the recipe directly into your collection for easy access.
  • Technical foundation. This could be an entire post in itself (and probably will be at some point) so I won’t go into too much detail here. But suffice it to say: the new ProjectNom is better organized for maintenance and expansion. One example: an API layer to keep the back-end concerns separate from the front-end.

There are bound to be bugs in this first version, but I’m committed to getting them ironed out. If you find anything amiss, please let me know either here in a comment, or at support@projectnom.com. I also welcome any feature ideas — I want to keep improving ProjectNom until it’s the ultimate recipe management solution across all platforms and all devices.

And on that note, I wanted to end with some musings on the state of recipe tech in general. Recipe web sites have improved greatly since I first started working on ProjectNom, but they’re still distinct silos of information. So even if a website lets you save recipes into a personal “recipe box”, you still have to remember which site has the recipe you’re looking for.

It’s also interesting to me that a lot of recipe management tools available today are locked to a particular platform. To be clear, they’re great apps — but if you’re not tied exclusively to Apple or Microsoft devices, then you suddenly lose access to your recipe collection. I certainly see the allure of staying on one platform — recipes are complex beasts, and it helps to harness the power of a specific platform to get the best experience. I certainly haven’t ruled out the possibility of creating platform-specific interfaces to ProjectNom. But the ability to access a recipe from any device at any time is a critical feature, and should never be compromised.

With ProjectNom, you can rest assured that it never will.

Now get out there and start cooking!

Tagged , , , , ,

Apple Watch First Impressions: Beyond the Obvious

Apple Watch
Much has been said about the Apple Watch already. Much of it has been obvious, especially if you’ve been following along with the technology media’s obsession with the device. In light of this, I wanted to write up a quick blog post detailing my experience with the Watch over the past week, while trying to avoid some of the common threads that you’ve likely already read.

Continue reading

Tagged , , , ,

Trowl 0.8, Or How I Learned to Stop Worrying and Love API 1.1

Overdone jokes aside, I realized that I use Trowl too much, and I invested too much time in the app, to just toss it aside when Twitter decided to finally turn off version 1 of its API. That said, it’s not a trivial process.

Technical Details

If you’re interested, Trowl was originally built on requesting XML responses from Twitter. This was primarily because, at the time, .NET was best suited for parsing XML documents more than anything else. With the switch to API 1.1, Twitter completely dropped the ability to request XML responses, in favor of JSON. Luckily, .NET has become more fluent with JSON recently – but it still meant going back through all my calls to Twitter and replacing the XML-based response/parse logic with JSON-based response/parse logic.

The Status of 0.8

Okay, so, I’m making this version 0.8 because I’m essentially changing the entire foundation that Trowl is based on. There aren’t any new features here, but it’s a big enough infrastructure change that I felt justified in bumping up the version to 0.8.

I’ve converted almost everything over to API 1.1 except the following:

  • Controlling Trowl through DMs (which I don’t think anyone uses anyway)
  • Spam reporting
  • Additional caching in light of API 1.1’s stricter rate limiting

Everything else should work, but I’m releasing a development version first so people can try it out and let me know what bugs they find.

Trowl 0.8 Download

UPDATE 6/17/2013+ Another quick update that fixes some issues with retrieving friends (darn those stricter rate limits!). Authorizing a new account should be fixed now too.

UPDATE 6/17/2013 All functions should be switched over to API 1.1 now, but please send me any bugs or issues you find. Thanks!

Thank you for sticking by Trowl during the API shutdown!

Tagged , ,

Microsoft Surface

I just watched Microsoft’s first commercial for their Surface tablet. It sucks. I get it: you’re so very proud of that “perfect click” sound you engineered – but that’s not what you center a commercial around.

This is the Microsoft Surface commercial that I would make.

Opening shot shows an iPad on its home screen. “This is an iPad,” starts the voiceover. “All your apps are laid out, ready to launch.” The camera zooms in on one of the icons, “You tap here to see your appointments for today.” Switch to another icon. “Tap here to get updates from your friends.” Switch to another icon. “And you can tap here to see the weather – because it’s not actually 73 degrees and sunny.”

Camera switches to another tablet. This time it’s Surface. “This is the new Microsoft Surface,” says the voiceover. “All your apps are laid out, ready to launch.” The camera zooms in, showing all the information right on screen that would have required tapping and hunting on the iPad.

“But you don’t need to. Looks like I’ll need an umbrella for the football game today.”

End.

Sure, Apple’s new iPod commercial is just as useless as the Surface commercial. But they can get away with that because everyone knows how an iPod works. The Surface is still an unknown to most people. I hope Microsoft gets more serious about their marketing as time goes on.

Tagged , ,

Trowl 0.7.3 (Tweet Marker Support)

I’m making a development version of 0.7.3 available for anyone who would like to help me test it before it’s released to everyone. This version has better stream error recovery handling, and makes a couple of other minor under-the-hood changes. The most noticeable change, though, is Tweet Marker support.

What is Tweet Marker?
For those who aren’t yet familiar with the service, Tweet Marker syncs your Twitter timeline across applications by recording the last tweet you read. Applications that support Tweet Marker can quickly jump straight to the spot in your timeline where you left off.

How does that work for Trowl?
Since Trowl doesn’t show you a traditional Twitter timeline (at the moment…), it uses this information a little differently.

Saving your last read tweet: in Trowl, a tweet is considered “read” when its notification is dismissed. Every 15 seconds, it will send the tweet that was last dismissed to Tweet Marker.

Retrieving your last read tweet: every 5 minutes, Trowl will pull the latest Tweet Marker from the server. It can’t remove any tweet notifications already on screen, but it won’t send new notifications for tweets you’ve already seen. Instead, it will pick up with the first new tweet. Depending on your settings, this will also happen when you uncheck the Silence option.

How do you enable Tweet Marker?
Tweet Marker has been added as one of the “missed tweets” options:

Tweet Marker

This option covers both tweets and mentions.

I’ve also updated Metro Display slightly. In addition to the coalescing support that I added a little while back, I also changed it so that the Twitter timestamp dynamically updates – this should have been added a long time ago, so I apologize for the wait. I also fixed a nasty memory leak bug.

You can try both of the new toys here:

Trowl 0.7.3 Development
Metro Display 1.5 Development

Please send any bug reports my way – you can comment here, send me a tweet, or post to the Google Group. Have fun. :)

Tagged , , ,

Windows 8: First Impressions

Windows 8

I’m still playing with the Windows 8 beta – er, sorry, “Consumer Preview” — and exploring its details. However, first impressions are important, and I think I have a few key observations to make.

Last year, I downloaded the developer preview when it became available. I didn’t spend too much time with it, because it was obvious how early of a build it was. Still, the Metro interface was promising, and the few apps that were available showed great promise.

One big complaint about the developer preview, though, was how much it relied on touch. You could use a mouse and a keyboard, sure, but they were second-class citizens. On the one hand, this was good: Microsoft desperately needed a good touch interface, and there was nothing better than Metro. But on the other hand, it was awkward to use on a traditional computer. Considering that this was the next version of Windows, that was a major problem.

Since then, Microsoft has attempted to soothe everyone’s fears with a consistent message – a message that was reiterated by Steven Sinofsky at the Consumer Preview launch event:

Sinofsky’s concern, however, is ensuring developers are on the same page, designing Metro apps that work just as well on touchscreens as they do with a mouse.

"The goal should be that the operating system scales with you," added Sinofsky.

"That’s what we mean by a no-compromise experience."

For the Consumer Preview, they took many steps to help make the Metro interface more intuitive for the keyboard and mouse, so that there were no compromises. But were they successful?

Continue reading

Tagged , ,

Restoring a Time Machine Backup That’s Stored on a Windows Home Server

I think that may be the longest title I’ve ever had on my blog. Anyway…

Last week, my Macbook Pro hard drive suddenly died after about two years of dedicated service. While it’s a bit surprising, I did push the little guy pretty hard – I regularly run OS X alongside Windows 7 on VMWare, and I keep both platforms busy. Considering the VM is actually running off the Boot Camp partition, it’s a lot of work for the drive. But I digress.

I’ve been running Time Machine for a while, so I was hoping that I could restore its most recent backup onto my replacement hard drive. There was just one snag, and I knew this would be an issue: my Time Machine backups are stored on my Windows Home Server. As you can imagine, doing this is a bit of a hack (here are some instructions if you don’t know how to do this yet). And as I’m sure you can also imagine, doing a hack like this makes a “normal” Time Machine restore impossible.

What I mean by “normal” is that if you boot off the OS X installation disk, you can choose to restore a Time Machine backup instead of installing a brand new copy of the operating system. When you choose this option, it scans for a Time Machine backup – but only the locations it supports, like external hard drives or Time Capsules.

Now, sure, you could probably copy the Time Machine backup on your WHS to an external drive, since the backup on the WHS is essentially just a disk image. But I didn’t have an external drive large enough to spare – and besides, there must be a better way.

Perhaps my Googling was sub-par at the time (I was in the middle of restoring my laptop, after all), but it took a while to find the answer. You can’t do a normal Time Machine restore, but you can use the Migration Assistant:

  1. Install a brand spanking new copy of OS X. Make a throw-away account when you’re prompted to create your first account. I named mine “Admin”.
  2. Mount your Time Machine backup. In Finder, use the menu option Go –> Connect to Server…, and type the address to your Windows Home Server. This is usually “smb://” plus the server’s name. (For me, it was “smb://beat”.) OS X should find the server, connect to it, and list the available shares. Connect to the share that contains your Time Machine backup, and double-click the .sparsebundle file to mount the disk. (Enter your Home Server username and password, if prompted.)
  3. Run Migration Assistant. This tool is located under Applications –> Utilities on your Mac hard drive. Start it up, and read the intro if you’d like. Click Continue when you’re ready. Choose the “From a Time Machine backup or other disk” option, and click Continue. If you were able to mount the disk image in step #2, then it should be an option to choose from. Select it and Continue.
  4. Restore the backup. The last screen shows you the available items to restore, and how much space they’ll take up. If you created the throw-away account in step #1, then there should be no conflicts with restoring your real account from the Time Machine backup. Select what you do or do not want to restore, and then click Continue. The restore can take a while depending on how much data you have.

That’s it! When it finishes, your account should be back to the way it was, exactly as you left it (as of the last Time Machine backup). You can now throw away your throw-away account, or leave it as a battle scar.

Since it took me a while to find this on Google, I’m writing it up to hopefully give it more exposure. And if this is already well know and I just missed it, well – at least now I have a record in my blog of when my hard drive failed and I was sad. :P

Tagged , , , , , ,