I waste almost 15 minutes a week on an annoying little task. Every email I send to an email list gets a link that says, "Click here to share with a friend."
Clicking the link opens a filled-in email message. Add an address or two, click send, and you're done. You share interesting information with your friends and my emails don't become one of those "Fwd: fwd: re: fwd: kids do the darnedest things!" disasters.
Nobody likes a forward, but everyone likes a direct share. It's different.
Making a direct share link, is as easy as using mailto:?subject=some%20subject&body=some%20very%20long%20body
. This type of URL makes a magic email link containing the subject some subject
and the body some very long body
.
One downside is that the subject and body are URL encoded. Finding an online URL encoder, pasting things in, and copying things out is a long process. Every time you want to change the body or fix a typo in the subject, you have to repeat the process all over again.
Another downside is that the body doesn't support HTML. You must strip it out or change it to Markdown.
This task was annoying me so much that I decided to build ShareThisEmail, a tiny tool that takes a subject and a body, and returns a mailto: link.
Simple, right?
Doing it simply
Yes. It takes just three lines of JavaScript to make the link:
function the_thing (subject, body) {
var subject = encodeURIComponent(subject),
body = encodeURIComponent(
toMarkdown(
body.replace(/(?:\r\n|\r|\n)/g, '<br>')
)
);
return "mailto:?subject="+subject+"&body="+body;
},
Well okay, nine lines with good formatting.
With this function, you encode the subject, encode the body, put the two together, and return the link. Dom Christie made an awesome JavaScript markdown converter that makes this function easy.
You can use jQuery and a "Do The Thing" button, to drive this function with one line of code:
$("#do-the-thing").click(function () {
$("#link").attr("href", the_thing($("#subject").val(), $("#body").val())
});
That's the JavaScript. Slap together some HTML and you're done. An hour after you started, the utility works, you push it online, and never have to be annoyed by the annoying task again.
And it only took so long because you had to look something up, opened Imgur by reflex, and spent twenty minutes clicking next.
Doing it reactifluxily
On the other hand, maybe you're like me. Maybe you thought to yourself, "I can use Flux and React and play with new stuff!".
This version took me two hours to build and at least an hour to get custom domains working with Github pages. Because this needs a domain of its own. Naturally.
But! It makes a great simple example for how to use Flux. The basic kind, at least; I haven't played with all the fancy alternatives yet.
Let's build our utility from the bottom up: start with input fields, add the data store, write the output component, and finally a component that puts it all together.
Fluxy input fields
The utility we're building makes a mailto:
link with a subject
and a body
. That means we need two inputs - an input
for the subject, and a textarea
for the body.
It takes 19 lines of nicely formatted JSX to build the subject input field:
// src/Subject.jsx
const Subject = React.createClass({
update: function (event) {
Actions.update_subject(event.target.value);
},
render: function () {
return (
<div class="form-group">
<label for="subject">Subject:</label>
<input type="text" class="form-control" id="subject" placeholder="Paste your subject line" value={this.props.value} onchange={this.update}>
<p class="help-block">This will become the prefilled subject line.</p>
</div>
);
}
});
Our component returns a normal input
field with a type, a class, and a placeholder. The change event is bound to this.update
with onChange
, and the value
is set to this.props.value
.
This combination is where the magic happens.
Whenever a user types into the input field, React calls this.update()
, which in turn calls Actions.update_subject
. Actions is a global collection of helper functions that trigger events on the dispatcher.
We use Actions
because it makes our code shorter. It looks like this:
const Actions = {
update_subject: function (subject) {
Dispatcher.dispatch({
actionType: Constants.UPDATE_SUBJECT,
subject: subject,
})
},
update_body: function (body) {
Dispatcher.dispatch({
actionType: Constants.UPDATE_BODY,
body: body,
})
},
}
Just an object with some functions. When our component calls update_subject
, it dispatches a new event on the dispatcher singleton with the correct actionType
, and the new value.
We'll look at what happens after dispatching in the next subheading. It involves setting the value
property to this.props.value
, so don't forget about that.
Great, we've got the Subject
component. The Body
component looks the same, except it's a textarea
:
// src/Body.jsx
const Body = React.createClass({
update: function (event) {
Actions.update_body(event.target.value);
},
render: function () {
return (
<div class="form-group">
<label for="body">Email Body:</label>
<textarea id="body" style="{{height:" "300px"}}="" class="form-control" placeholder="Paste your email body here. HTML is fine, we'll convert it to a text-only format." value={this.props.value} onchange={this.update}> <p className="help-block">This will become the prefilled body.</p>
</div>
);
}
});
See? Almost the same.
We return a textarea
, bind the change event to this.update
, and set the value with this.props.value
.
Now, you might be thinking two things:
- "This is dumb; you're making React look bad by repeating code." Technically, you're right, but these two are semantically different enough to warrant separate components.
- "You're dumb; React 0.14 came out last week and this code looks old." You're right. React 0.14 came out the day after I wrote this code. Deal with it. (⌐■_■)
The Data store
Great, we've built the components for our users to interact with, and we've got onChange
events firing actions into the aether.
We're using the default implementation of Flux provided by Facebook. That means we only have to declare the global dispatcher once, and then we never have to worry about it again.
// src/Dispatcher.jsx
const Dispatcher = require("flux").Dispatcher
module.exports = new Dispatcher()
The dispatcher acts as a sort of event broker between components. That makes it easier for components at various levels of the architecture to communicate with the data store, which acts as the single source of truth.
The store consists of three parts:
- Variables/objects with data
- The Store object, which extends EventEmitter
- A register call to the dispatcher that listens for events
We start the data store off with some global variables.
// src/Store.jsx
var subject = "",
body = ""
We're relying on the way Webpack bundles/compiles JavaScript to keep these from becoming true globals. We're also betting on the fact that require()
is referentially transparent and always returns the same instance of the code.
That's a fancy way of saying "Trust me, this works and is okay to do".
The store itself comes out to 20 lines of code. Here's what it looks like:
// src/Store.jsx
const Store = Object.assign(EventEmitter.prototype, {
getState: function () {
return {
subject: subject,
body: body,
}
},
addChangeListener: function (callback) {
this.on(CHANGE_EVENT, callback)
},
removeChangeListener: function (callback) {
this.on(CHANGE_EVENT, callback)
},
emitChange: function () {
this.emit(CHANGE_EVENT)
},
})
We're using Object.assign
to extend EventEmitter
with four extra functions: getState
returns an object with the current values, add-
and removeChangeListener
add or remove a listener on the change event, and emitChange
triggers the change event.
The latter three make our other code easier to write and read.
Finally, we register our store with the dispatcher:
// src/Store.jsx
Dispatcher.register(function (action) {
switch (action.actionType) {
case Constants.UPDATE_SUBJECT:
subject = action.subject;
Store.emitChange();
break;
case Constants.UPDATE_BODY:
body = action.body;
Store.emitChange();
break;
default:
// no op
};
This is no fancier than thing.on('event', callback)
. The hard work happens inside the dispatcher; we only care to be notified when something happens.
Our callback then takes the actionType
property, and decides what to do. In most cases, it will change the value of a store variable and call emitChange
.
That's it. Our store is ready. Let's see what happens after emitChange
.
The output
We've got the inputs, and we've got the data store as the single source of truth for the entire utility. Now we need a component that will act as the output. Some way for the user to get that mailto:
link.
We'll call this component Link
because we are engineers and have amazing imaginations.
// src/Link.jsx
const Link = React.createClass({
build_link: function () {
var subject = encodeURIComponent(this.props.subject),
body = encodeURIComponent(
toMarkdown(this.props.body.replace(/(?:\r\n|\r|\n)/g, "<br />"))
)
return "mailto:?subject=" + subject + "&body=" + body
},
render: function () {
return (
<p>
You're done! Copy this into your email:{" "}
<a href={this.build_link()} target="_blank">
Email this to a friend
</a>
</p>
)
},
})
The output is some text with a link, which gets its href
attribute from the this.build_link
method.
Remember the build_link
method from earlier? Same function, different name and different way of getting data.
Our vanilla function used arguments to get the raw subject and body; this one gets them from this.props
. These are populated as element properties by the parent component. Let me show you how it's done.
The main component puts it all together
We've got the building blocks: the inputs, the data layer, and the output. Now we have to put them all together.
ShareThisEmail is a component with more code than the others, but it's not too bad.
// src/ShareThisEmail.jsx
const ShareThisEmail = React.createClass({
getInitialState: function () {
return Store.getState();
},
componentDidMount: function () {
Store.addChangeListener(this._onChange);
},
componentWillUnmount: function () {
Store.removeChangeListener(this._onChange);
},
_onChange: function () {
this.setState(Store.getState());
}
render: function () {
return (
<div>
<form className="col-md-8">
<Subject value={this.state.subject} />
<Body value={this.state.body} />
<Link subject={this.state.subject}
body={this.state.body} />
</form>
</div>
)
}
});
This component uses addChangeListener
to bind to data store changes when it's mounted into the page, and ensures there are no hanging callbacks before it's unmounted. Basically, subscribes to changes when it starts existing, and unsubscribes before it dies.
In both cases, we use this._onChange
as the callback. It uses Store.getState()
to naively set ShareThisEmail
's internal state to whatever the store says. Not the best approach in all cases, but it works for us.
Once this.setState
executes, it triggers a component re-render. This is where React magic kicks in.
On every re-render, we feed the new state into Subject
, Body
, and Link
components as properties. Those components then see the new values in this.props
and re-render as needed. A re-render only happens if the actual values are different.
So for example, Link
re-renders every time the user types into either subject or body. But subject only re-renders when the subject changes.
Now here's the kicker: because we're using so called controlled components, their values can only change by changing this.props.value
. If you forget to close the Flux loop, typing into an input field doesn't change the field.
That part seems kind of bonkers, but it makes sense when you think about it. We're trying to control everything the user sees through an internal data representation. It gives us many benefits, I promise.
So, there you have it. A working app. I only showed you the core parts of the code, so you might want to check out ShareThisEmail's source on Github to learn the details.
Conclusion: do you need flux?
After reading all of that, you might be asking yourself: why did we turn 10 lines of JavaScript into 137, and added a few hundred kilobytes of library payload to our page?
To prove a point.
For this example, Flux doesn't solve any problems. It makes the code harder to follow, increases our payload size, and provides a fertile ground for bugs. More code, more places to make mistakes.
But Flux is a pattern. You can give this code to anyone who knows Flux, and they will know exactly where to look for what. They'll know how things fit together, and they will be able to add more features.
Five months from now, you'll still know what's going on, and you'll be able to fix things.
Adding new features doesn't mean rewriting the whole app. In fact, I added a "Copy to Clipboard" button over the weekend to improve UX and ... well, Flux made it kind of boilerplatey.
When I think of my main gig's production code with its thousands of lines of JavaScript, tens of components, and hundreds of sources of truth, my soul weeps for Flux.
Your mileage may vary.
PS: I'm working on a book about testing React code, send me an email, if you're interested
Thanks to Will Fanguy, Tiffany Morgan, David Bairne, and Coletta Teske for reading draft copies of this article
Continue reading about React+Flux can do in just 137 lines what jQuery can do in 10
Semantically similar articles hand-picked by GPT-4
- Are you annoyed by the extra space after your name [name|]?
- Custom markdown extensions with Remark and HAST handlers
- How to structure your MobX app for the real world
- Oh the things you can learn with a fun weekend hack project 🏗
- The unreasonable difficulty of adding a feature to a Textarea
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️