paint-brush
Reduce, Reuse, Reactby@donavon
1,555 reads
1,555 reads

Reduce, Reuse, React

by November 21st, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

<em>NOTE: This is a cross-post that originally appeared in </em><a href="https://www.javascriptjanuary.com/" target="_blank"><em>JavaScript January</em></a><em>.</em>

People Mentioned

Mention Thumbnail

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Reduce, Reuse, React
 HackerNoon profile picture

A beginners guide to turning that little reusable piece of code into its own npm package.

NOTE: This is a cross-post that originally appeared in JavaScript January.

Background

A while back while writing a React application that hit a REST endpoint, I wrote a little utility to build a properly encoded Uri path. At the time, I didn’t think much about it. It did the job and I moved on.

Months later, while working on another project, I needed something similar. I set out to find it and copy the code, but I couldn’t remember where I originally wrote it.

After a good deal of digging around — both on my local file system and on GitHub — I finally located it. I vowed that this would never happen again.

Reduce

As I mentioned, the utility builds a Uri path. For example, given a resource of “user” and a userId of “123”, it might return a path to a specific user resource as follows: "/users/123".

The casual reader might be saying, “Why can’t you just concatenate the userId to the end of the string "/users/"and call it a day?” Maybe something like this.



const resource = 'users';const userId = '123';const path = `/${resource}/${userId}`;

That would be fine if you could swear on your mother’s grave that the name of the resource or the userId did not contain any characters that needed to be Uri encoded — not only today, but forever.

What if userId values weren't just numbers, but handles, like "bob", "jim", etc. And what if there was a handle such as "this&that"? This would produce a path of "/users/this&that" which is an invalid URL.

Or what if, for example, the user entered their userId as "?format=xml"? path would end up being "/users/?format=xml", which would likely return all users in XML format—not at all what we're expecting.

We could solve the Uri encoding issue in the previous example like this.



const resource = encodeURIComponent('users');const userId = encodeURIComponent('123');const path = `/${resource}/${userId}`;

Perfect! It works. But…that’s a lot of effort each time you want to generate a path.

This is why I came up with a utility that you hide away from your main logic and can re-use over and over. It incorporates the very useful, and often misunderstood, Array.reduce method, which takes an array of values and reduces them down to a single value. In our case, we take an array of strings and values, and reduce them to a single string.





const buildUriPath = (strings, ...values) => (strings.reduce((partialUri, string, i) => (`${partialUri}${encodeURIComponent(values[i - 1])}${string}`)));

Use it as shown here and all that work of encoding each variable is abstracted away.



const resource = 'users';const userId = '123';const path = buildUriPath`/${resource}/${userId}`;

Briefly, it takes two arrays: strings (the string constants ['/', '/', '']) and values (the templated string values that need to be encoded ['users', '123']). It constructs and returns a single string "/users/123".

OK, that’s not entirely accurate. It is passed an array of strings, but what I’m calling an array of values, is actually a variable number of arguments. I’m using ES6 “rest” syntax to convert the arguments into an array of values.

When we reduce over strings, the zeroth element of the strings array is passed as partialUri and iteration starts with the first element, and so on.

There are no parentheses behind the call to buildUriPath? What is this witchcraft? This is called a Tagged Template Literal. It’s a special form of the ES6 template literal that we used above, but it allows us to process it with a function.

Remember that we don’t call our code directly. It’s called by the JavaScript’s template literal function after parsing the template into separate elements.

Fans of styled-components are already familiar with this syntax. It’s a very powerful addition to the ES6 specification.

Reuse

So now that I have a super-duper, handy-dandy buildUriPath function, let’s share it with the world so that _everyone_can use it. But the thing is... Even if no one else wanted to use this, I still want to be able to use it myself, over-and-over easily. We can do this by making an npm package. I’m rather keen on the name build-uri-path, so I hope that it’s available... and it is!

Because we wrote our utility in ES6, but the world still relies on ES5, we’ll use Babel to transpile it for us.

The complete source for everything described below can by found in my GitHub repo.

GitHub

First create a GitHub account (if you don’t already have one) and then create a blank repo. Clone it to your local drive and change into the directory. Execute npm init to create a skeleton package.json. Be sure to set the entry point to lib/index.js. This will add the following to your package.json file, which instructs the consumer of your package what file to initially execute.

"main": "lib/index.js",

Install Babel

We will be writing our code in ES6, but the reality is that today’s browsers don’t fully support the syntax that we write in. Fortunately for us all, there is a utility called Babel that allows us to transpile ES6 source into ES5 distributable code.

To install Babel, execute the following in a terminal of your choice.

$ npm install -D babel-cli babel-preset-env

You’ll also need to create a .babelrc file. This tells Babel how to transpile.



{"presets": ["env"]}

Get coding!

Open you favorite editor and create a src folder with an index.js file. This will be where we place our buildUriPath function which will be exported as default.

The entire source looks like this.





const buildUriPath = (strings, ...values) => (strings.reduce((partialUri, string, i) => (`${partialUri}${encodeURIComponent(values[i - 1])}${string}`)));

export default buildUriPath;

Build

Run npm run build and you should see Babel build a lib/index.js file. Peek inside of this file if you want to see the ES5 transpiled code. Notice that it is substantially more verbose than it’s ES6 source. This is the syntactic sugar that ES6 brings to the table.

For larger packages consider using a packager such as rollup.js, but for our small package, publishing ES5 code should be just fine.

Testing

As a developer, you must prove to the world, and to yourself, that your code works under a wide variety of input values by writing lots of tests! They are also critical for regression.

Testing is an entire article unto itself. You can read more about JavaScript testing in the January 1st JavaScript January article “But really, what is a JavaScript test?

You can, however, see that the tests pass for our package by running npm test.

Continuous Integration

You should really consider setting up Continuous Integration (CI) such as CircleCI or Travis. Tests will be run automatically on each push to your code repo. This will help ensure code quality when you have multiple source code contributors.

Again, CI is beyond the scope of this article, but you can find a rather comprehensive explanation of setting up Travis CI here.

Linting

Setting up a linter, such as ESLint will also help you reduce errors by catching issues before you compile and test. You may want to consider using the popular ESLint configuration from Airbnb.

Scripts

We’ll need to add a few scripts to our package.json file to automate things. Add a build command to allow us to manually transpile our code. Add a prepublishOnly hook so that we can be sure that the tests pass and our code is transpiled automatically whenever we execute npm publish.

Our scripts section looks like this.





"scripts": {"prepublishOnly": "npm test && npm run build","build": "babel src --out-dir lib --ignore '**/*.test.js'","test": "eslint src && jest"},

TypeScript support

If you really want to impress, you can optionally add a TypeScript type definition file (the so-called d.ts file) so that TypeScript users can have first class type support.

Just create the file types/build-uri-path.d.ts, in our case. The file would look like this.







declare module "build-uri-path" {function buildEncodedUri(strings: string[],...values: string[]): string;export default buildEncodedUri;}

You also need to reference the file from within your package.json file by adding the following line.

"types": "types/build-uri-path.d.ts",

See the TypeScript documentation for complete details.

And finally… Publish to npm

All that’s left to do is publish to npm. But first you must be a user on the npm registry. If you don’t have an account, create one with npm adduser. Once an npm user has been created, simply type npm publish. The prepublishscript will run—which tests and builds—and if successful, you will have published your first npm package, my friend!

React

Now anyone can use your shiny new package. So why don’t we be the first? I’ll use CodeSandbox to install build-uri-path and write a simple React app based on the Component Folder Pattern that uses it.

The app hits a REST endpoint to fetch some data. I’m using the Star Wars API as my back end. The front end React app allow the user to type in a resource and an ID. As these are coming directly from an untrusted source (i.e. the user), they need to be encoded. Good thing there’s an npm package that will do just that!

You can see the complete app, with source code, running on

.

You can see the complete app, with source code, running on

.

The file loadData.js imports our buildUriPath function and uses it to build the path to send to the backend REST API.



import buildUriPath from 'build-uri-path';...const path = buildUriPath`/${resource}/${id}`;

Conclusion

You don’t have to write the next React to contribute a package to the Open Source community. If you see value in your work, then get it out there. Even if you are the only one who benefits, that’s reason enough.

But you may be surprised. Others may find it useful, and your meager little package could be the next open source sensation!

Thanks to Paul Bouzakis for helping with the TypeScript section and especially to Renato Selenica for fixing my mistaakes. ;)

I work at American Express and write for the American Express Engineering Blog. Check out my other works and the works of my talented co-workers at AmericanExpress.io. You can also follow me on Twitter.