Today I Learned

ins8s

42 posts about #javascript

Reach Into An Object For Nested Data With Get

Among the many lodash utilities is _.get for getting data from nested objects. You can specify where to reach into the nested data of an object using a path.

Consider the following awkwardly nested object:

const resp = {
  error: {
    errors: [
      { message: "Something went wrong" },
    ],
  },
};

Here is how we might reach into this with vanilla JavaScript:

resp.error.errors[0].message;

Reaching into this for the message value is tricky because as soon as the resp object contains differently nested data, an error is likely to be thrown. We can simultaneously avoid a bunch of exception handling logic and provide a default value with the _.get function:

_.get(resp, 'error.errors[0].message', 'Default error message');

If we decide to not include a default value, then undefined will be used.

Mock A Function With Return Values Using Jest

Jest provides a collection of utilities for working with mocked functions. To create a mock function, do:

jest.fn()

// assign it to a variable
const fakeFunc = jest.fn();

// pass it as a prop
<SpecialInput handleChange={jest.fn()} />

A mocked function can then be attributed with a return value.

const fakeFunc = jest.fn();
fakeFunc.mockReturnValue("hello");
fakeFunc(); // => "hello"

The mockReturnValue() function ensures that the value is returned whenever your function is called.

We can also limit the return value to occurring just once.

const fakeFunc = jest.fn();
fakeFunc.mockReturnValueOnce("hello");
fakeFunc(); // => "hello"
fakeFunc(); // => null

mockReturnValueOnce() ensures the value is returned once and all subsequent calls yield null.

Test Coverage Stats With Jest

Jest is a delightful tool for JavaScript testing from Facebook. As your project evolves and you add tests (or perhaps choose to not add tests) you may wonder what kind of test coverage you have. What is the overall coverage? Which files are well covered and which are seriously lacking?

Use the --coverage flag to get this information.

$ jest --coverage

After running all of your tests a table will be output to the terminal with a list of files and their respective coverage percentages in terms of statement, branch, function, and line coverage.

---------------------------|----------|----------|----------|----------|----------------|
File                       |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
---------------------------|----------|----------|----------|----------|----------------|

This can help guide you to what parts of your app may need more testing.

Scoping Variables With A Block Statement

A solid way to keep code readable and easy to understand is to ditch the terse one-liners in favor of breaking things up across multiple lines and using self-documenting variable names. This isn’t all good though. Each new variable that is defined is a handle on some data that can be misused later in the syntax scope.

JavaScript has a nice way of being clear about the scope of certain variables:

let parsedDate;
{
  let [month, day, year] = input.split('-');
  parsedDate = new Date(year, month, day);
}

// do something with parsedDate

The month, day, and year variables are scoped to the { ... } which is a block statement. This helps communicate and enforce that those variables are only to be used in that very specific context. Other developers and our future selves won’t be able to erroneously use those variables.

Of course, breaking out a function is another way of accomplishing this. Sequestering code in a different part of the file is not always the best answer though. Sometimes you want it locally fenced off. For those times, use a block statement.

Get The Location And Size Of An Element

All modern browsers ship with the getBoundingClientRrect() function. It can be invoked for any DOM element. It returns the x and y coordinates of the element within the browser viewport as well as the height and width values and the top and bottom values along the y-axis and left and right values along the x-axis.

> $0.getBoundingClientRect()
{
  "x": 381.421875,
  "y": 70,
  "width": 1030.578125,
  "height": 48,
  "top": 70,
  "right": 1412,
  "bottom": 118,
  "left": 381.421875
}

For instance, this is the result of invoking it against a header element on the MDN page linked above.

Spread The Rest With ES6

The spread operator provided by ES6 is a powerful syntactic feature. One way it can be used is to capture the rest of an object or array in a variable.

const pokemon = ["Charmander", "Squirtle", "Bulbasaur"];
const [first, ...rest] = pokemon;

console.log("Remaining: ", rest); // Remaining: ["Squirtle", "Bulbasaur"]

const gymLeaders = {
  brock: "rock",
  misty: "water",
  surge: "electric",
  erika: "grass"
};
let { brock, erika, ...otherLeaders } = gymLeaders;

console.log(otherLeaders); // Object {misty: "water", surge: "electric"}

Using this spread destructuring we can capture the remaining parts of an array or object in a variable. We can also use this syntax in a function signature to grab specific items from an incoming object argument without losing track of the rest — this is especially useful in React.js development when dealing with incoming props.

This is a stage 4 feature and may not be available in your particular environment.

See a live example here.

Custom Type Checking Error Messages With Yup

In Yup Schemas Are Validated Asynchronously, I showed how to create a simple schema that allows you to enforce that a value is a number.

const numSchema = yup.number();

If we use this schema to validate something that isn’t a number, Yup will provide a lengthy default message. Here is what we get if we validate against 'hey':

this must be a number type, but the final value was: NaN (cast from the value "hey").

This value isn’t necessarily suitable for displaying to a user. We can customize the type checking error message by redefining our schema with the typeError() function:

const numSchema = yup.number().typeError("Invalid number");

Yup Schemas Are Validated Asynchronously

Yup provides a flexible object schema validation DSL. For instance, if you want to enforce that a certain value is a number, you can define something like this:

const numSchema = yup.number();

You can then validate anything against that schema.

const validator = (val) => {
  numSchema.validate(val)
    .then(result => {
      console.log(result); // it is the value of `val`
      return true;
    })
    .catch(error => {
      console.log(error.errors); // array of validation error messages
      return false;
    });
};

The validation is async, so if it succeeds the then block is hit. If the validation fails, it will fall through to the catch.

validator(5) // => true
validator('what') // => false

Link A JavaScript Package Locally

If you are putting together a JavaScript package and you’d like to test it out locally before putting it on NPM, use npm link.

First, from the directory of the package you are creating run:

$ npm link

This will symlink the package to the global node modules directory.

Then, from the base project directory that you want to try importing and using the package from, run:

$ npm link name-of-package

This will create an additional symlink from the global node modules directory to the node_modules of this target project.

You’ll now have access to the project, try an import to get what you need and try it out.

See man npm-link for more details.

List Top-Level NPM Dependencies

The npm ls command can be used to list all dependencies for a project. This will, however, produce an exhaustive list of all dependencies including dependencies of dependencies. A list this large probably isn’t going to be of much use.

The --depth flag allows you to restrict the depth of the dependency tree that is generated.

$ npm ls --depth=0

This will produce a list of only the top-level dependencies.

See man npm-ls for more details.

New Dates Can Take Out Of Bounds Values

You can create a new date by feeding it arguments for year, month, and day.

> new Date(2017, 11, 31)
Sun Dec 31 2017 00:00:00 GMT-0600 (CST)

What happens if we push the day value out of bounds?

> new Date(2017, 11, 32)
Mon Jan 01 2018 00:00:00 GMT-0600 (CST)

It rolls over to the next month.

Does the same happen when we push the month value out of bounds?

> new Date(2017, 12, 31)
Wed Jan 31 2018 00:00:00 GMT-0600 (CST)

Yep.

What about negative values?

> new Date(2018, -1, 31)
Sun Dec 31 2017 00:00:00 GMT-0600 (CST)

It rolls the month, and consequently the year, back.

Waiting On Multiple Promises

You may find yourself in a situation where you have to request multiple resources from multiple API endpoints and then combine that data in some way.

One way to achieve this would be with nested promises.

fetch('/blogs').then((response) => {
  let blogs = response.body;

  fetch('/tags').then((response) => {
    let tags = response.body;

    // combine blogs and tags ...
  })
})

This nesting isn’t ideal and it can get hard to read as the full implementation is put into place.

The Promise API provides an alternative.

let blogsPromise = fetch('/blogs')
let tagsPromise = fetch('/tags')

Promise.all([blogsPromise, tagsPromise]).then(([blogsResp, tagsResp]) => {
  // combine blogs and tags ...
})

With Promise.all() we are able to wrap any number of promises and wait for all of them to resolve until we do something with the results. This allows us to create a context in which we have all the data we need without a bunch of nesting.

Fill An Input With A Ton Of Text

I needed to test out a form validation for an input that should render an error when the length of the context exceeds 10,000 characters. Two small tricks make this easy.

First, you can target any DOM element via the Chrome dev tools by selecting it and then referencing it via the $0 magic variable. More details here.

> $0
<input>...</input>

Second, you can quickly and precisely generate a very long string with the repeat function.

> "a".repeat(10000)
"aaaaaaaaaaaaaaaaaaaaaaa..."

Combine these two tricks in the browser to fill the input with a ton of text:

> $0.value = "a".repeat(10000)

h/t Dillon Hafer

ISO-8601 Formatted Dates Are Interpreted As UTC

Using new Date() or Date.parse() with a string that represents a date is a great way to create a Date object for a specified date. A variety of formats are accepted by these methods.

But, caution!

There are subtle differences in how those dates will be interpreted. Given any old string that reasonably represents a date, the date will be interpreted using the local time zone, in my case CST.

> new Date('2017-12-4')
Mon Dec 04 2017 00:00:00 GMT-0600 (CST)

However, as soon as we use an ISO-8601 compliant date format, ECMAScript 5 specifies that the date ought to be interpreted using the UTC time zone. As you can see, the results are drastic enough to affect what day it comes out to.

> new Date('2017-12-04')
Sun Dec 03 2017 18:00:00 GMT-0600 (CST)

Source

Destructuring The Rest Of An Array

ES6 offers some amount of pattern matching on arrays. This means you can do fun stuff like grabbing a couple values and then destructuring the rest of the array into a variable.

> const kids = ["Mike", "Will", "Dustin", "Lucas", "Eleven", "Max"];
undefined
> const [first, second, ...rest] = kids;
undefined
> first
"Mike"
> second
"Will"
> rest
["Dustin", "Lucas", "Eleven", "Max"]

By using the ... syntax with a variable name in the left-hand side of the assignment, you are able to capture an array of whatever isn’t assigned to preceding variables.

Freeze An Object, Sorta

You can freeze a JavaScript object using Object.freeze which will help enforce some immutability practices. Don’t be fooled though, you can still modify arrays and objects in the frozen object.

Here is what the docs have to say:

The Object.freeze() method freezes an object: that is, prevents new properties from being added to it; prevents existing properties from being removed; and prevents existing properties, or their enumerability, configurability, or writability, from being changed, it also prevents the prototype from being changed.

And here is Object.freeze in action:

> const things = {one: "two", hello: "world", cats: ["Von Neumann", "Sosa"]}
undefined
> Object.freeze(things)
{one: "two", hello: "world", cats: Array(2)}

> things.one = "three"
"three"
> things.dogs = []
[]
> delete things.hello
false

> things
{one: "two", hello: "world", cats: Array(2)}

> things.cats.push("Sneaky")
3

> things
{one: "two", hello: "world", cats: Array(3)}

See the MDN Docs for more details.

h/t Jake Worth

String Interpolation With Template Literals

ES6 adds support for template literals. Template literals make it much easier to compose strings of content — string interpolation. They allow for single and double quotes without having to fuss with escaping. Embedded expressions are also supported which means you can avoid awkward-to-type string concatenation with the + operator.

Here is an example:

> let movie = 'it'
undefined
> `What's up, I just saw "${movie.toUpperCase()}".`
"What's up, I just saw "IT"."

Render An Array Of Elements With React 16

React 16 was released today. Among many exciting features and updates is support for rendering an array of elements.

This can look as simple as this example:

return [
  <li key="1">One</li>,
  <li key="2">Two</li>,
  <li key="3">Three</li>
];

It really shines in the case of generating elements from an array of data.

let data = [
  { value: "One", key: "1" },
  { value: "Two", key: "2" },
  { value: "Three", key: "3" }
];
return data.map(item => {
  return (
    <li key={item.key}>
      {item.value}
    </li>
  );
});

No need to wrap the result in a <div>!

source

Matching Multiple Values In A Switch Statement

Switch statements are a handy way to execute different branches of code based on a value match. This is often what is used in Redux reducers when updating the state in response to certain actions.

But what if you need multiple values to result in the same branch of execution without duplicating the code?

The execution of a switch statement falls through, so after one match, it will continue to try and do subsequent matches if you don’t interrupt the execution with a break or return. Conveniently, this solves our problem of matching multiple values.

switch (action.type) {
  case "UPDATE_NAME":
  case "UPDATE_DURATION":
    let newData = anotherReducer(state.data, action);
    return { ...state, data: newData };
  default:
    return state;
}

See the MDN docs for more details.

Create An Array Containing 1 To N

Some languages, such as Ruby, have a built in range constraint that makes it easy to construct an array of values from 1 to N. JavaScript is not one of those languages. Nevertheless, if you don’t mind the aesthetics, you can get away with something like this:

> Array.apply(null, {length: 10}).map(Number.call, Number);
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

That gives us 0 through 9. To get 1 through 10, we can tweak it slightly:

> Array.apply(null, {length: 10}).map(Number.call, n => Number(n) + 1);
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

To generalize this, we can replace 10 with N and then just expect that N will be defined somewhere:

> var N = 10;
=> undefined
> Array.apply(null, {length: N}).map(Number.call, n => Number(n) + 1);
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Source

Create Bootstrapped Apps With Yarn

The yarn cli comes with a create command that is a convenience command for generating bootstrapped apps that follow the create-<name>-app convention.

Want to create a React.js app using create-react-app, invoke the following command:

$ yarn create react-app my-app

Don’t already have a particular package globally installed? yarn create will install it for you. For instance, the following command with install and use create-vue-app:

$ yarn create vue-app my-other-app

h/t Gabe Reis

Yarn Commands Without The Emojis

If you are a hater and you’d like to run yarn commands without emojis being playfully included in the output, just include the --no-emoji flag. The output of a command like add will look like this:

$ yarn add chalk --no-emoji
yarn add v0.17.10
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 7 new dependencies.
├─ ansi-styles@3.1.0
├─ chalk@2.0.1
├─ color-convert@1.9.0
├─ color-name@1.1.3
├─ escape-string-regexp@1.0.5
├─ has-flag@2.0.0
└─ supports-color@4.2.0
Done in 0.54s.

See yarn help for details.

Default And Named Exports From The Same Module

ES6 module syntax allows for a single default export and any number of named exports. In fact, you can have both named exports and a default export in the same module.

Here is an example:

// src/animals.js
export default function() {
  console.log('We are all animals!');
}

export function cat() {
  console.log('Meeeow!');
}

export function dog() {
  console.log('Rufff!');
}

In this case, you could import the default and named exports like so:

// src/index.js
import animals, { cat, dog } from './animals.js';

animals(); // "We are all animals!"
cat();     // "Meeeow!"
dog();     // "Rufff!"

source

Expand Emojis With The Spread Operator

There are a number of emojis that are not stand-alone unicode characters, but instead are a combination of two or more other emojis. The two main places this happens is with family emojis and emojis using non-simpsons skin tones.

You can use JavaScript’s spread operator to expand these emojis to see what their base components are. Here is a screenshot of a few that I expanded from Chrome’s dev tools.

source

Timing Processes

If you want to time a process, you can use the console.time() and console.timeEnd() utilities specified by the console Web API. Invoking console.time() with a label starts a named timer. You can then run the process you want to time. Then invoke console.timeEnd() with the same label to terminate the timer and see how long the process took.

console.time('sorting');
[11,10,9,8,7,6,5,4,3,2,1].sort();
console.timeEnd('sorting');
> sorting: 0.278ms

console.time('console logging');
console.log('logging to the console');
console.timeEnd('console logging');
> logging to the console
> console logging: 0.311ms

console.time('adding'); 1 + 1; console.timeEnd('adding');
> adding: 0.006ms

These functions are implemented in most modern browsers.

See the docs for more details.

Running ES6 Specs With Mocha

If your JavaScript specs contain ES6 syntax, Mocha, by default, will not be able to interpret and run them. In order to run them with Mocha, you will need to tell Mocha to use something like Babel to compile them. The --compile flag can be used to point Mocha to the babel-core/register package.

$ mocha --compilers js:babel-core/register path/to/specs/*.spec.js

If you already have a test command specified in your package.json file, you can update it with the --compile portion of the above command.

This all assumes you’ve already setup your project with Babel and Babel presets.

source

Numbers Are Empty

The lodash project comes with a ton of handy JavaScript utilities including the _.isEmpty() function. This is great for checking if Arrays, Objects, and Strings are empty. The following is how this function is defined in the docs:

Checks if value is an empty collection or object. A value is considered empty if it’s an arguments object, array, string, or jQuery-like collection with a length of 0 or has no own enumerable properties.

Having not examined this definition too closely and because I primarily write Rails code from day to day, I conflated _.isEmpty() with the #blank? method provided by Rails’ ActiveSupport. This holds true for the most part, but quickly defies expectations when it comes to numbers.

> _.isEmpty(1)
// true

Accessing Arguments To A Function

The arguments object is available within any JavaScript function. It is an array-like object with all of the arguments to the function. Even if not all of the arguments are referenced in the function signature, they can still be accessed via the arguments object.

function argTest(one) {
  console.log(one);
  console.log(arguments);
  console.log(arguments[1]);
}

argTest(1);
// 1
// [1]
// undefined

argTest(1, 'two', true);
// 1
// [1,'two',true]
// 'two'

See the Arguments object docs on MDN for more details.

h/t Dorian Karter

Object Initialization With Shorthand Property Names

If I have some variables:

const one = 1,
  two = 2,
  three = 3;

and I’d like to initialize an object with them, I’ll generally do something like the following:

const obj1 = {
  one: one,
  two: two,
  three: three
};
// Object { one: 1, two: 2, three: 3 }

That seems pretty standard, but with ES6 comes a feature called shorthand property names which makes that look verbose and redundant. If you already have properly named variables, they can be used as a short hand for both the key name and variable value:

const obj2 = {
  one,
  two,
  three
};
// Object { one: 1, two: 2, three: 3 }

See the MDN Docs for Object Initializer for more details.

Immutable Remove With The Spread Operator

ES6 introduces the spread operator which allows you to expand arrays in place for function calls, array composition, array destructuring, etc. One thing the spread operator allows you to concisely do with array composition is perform immutable operations on arrays. For instance, to remove an item from an array by index, you can throw together the following function.

const remove = (items,index) => {
  return [...items.slice(0,index),
          ...items.slice(index+1,items.length)];
};

const list = [1,2,3,4,5];
remove(list, 2);
// [1,2,3,4]
list
// [1,2,3,4,5]

It only took a couple lines of code and immutability is baked in.

There may be a couple edge cases that are not handled in this solution (e.g. remove(list, -1)), but you get the general idea.

Computed Property Names In ES6

Perhaps more often than not, property names in an object are based on something dynamic rather than a static value. In ES5, you may end up with code like the following to build an object with dynamic property names:

var dynamic1 = 'hello',
  dynamic2 = 'world';
var obj = {};
var propName1 = dynamic1 + dynamic2;
var propName2 = 5 * 11;
obj[propName1] = true;
obj[propName2] = false;

> obj
// { 55: false, 'helloworld': true }

With ES6, we get computed property names which allow us to do all of these inline when defining the object. We just have to wrap the code that will be evaluated to a property name in brackets:

const dynamic1 = 'hello',
  dynamic2 = 'world';
const obj = {
  [dynamic1 + dynamic2]: true,
  [5 * 11]: false
};

> obj
// { 55: false, 'helloworld': true }

Computed properties are already available in many modern browsers.

Enable ES7 Transforms With react-rails

The react-rails gem adds JSX and ES6 transforms to the asset pipeline. By using .js.jsx and .es6.jsx extensions with relevant files, the asset pipeline will know to make the appropriate transformation when compiling application assets. ES7 transforms are not enabled by default, but can be configured. Add the following to the config/application.js file to allow ES7’s class properties syntax:

config.react.jsx_transform_options = {
  optional: ["es7.classProperties"]
}

h/t Mike Chau

Transforming ES6 and JSX With Babel 6

With Babel 5, transforming ES6 and JSX into ES5 code was accomplished by including the babel-loader. This would be configured in webpack.config.js with something like the following:

module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
    }
  ],
},

Now, with Babel 6, the different parts of the loader have been broken out into separate plugins. These plugins need to be installed

$ npm install babel-preset-es2015 babel-preset-react --save-dev

and then included as presets

module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
      query: {
        presets: ['es2015', 'react']
      },
    }
  ],
},

Alternatively, the presets can be specified in the project’s .babelrc file.

Source

Splat Arguments To A Function

Often times you have a function that takes a certain set of arguments. Like the following adder function:

var adder = function(a,b,c) {
  return a + b + c;
};

But you are left trying to pass in arguments as an array (e.g. [1,2,3]). You want to be able to splat the array of arguments so that it matches the function declaration. This can be done by using apply.

> adder.apply(undefined, [1,2,3])
6

Throttling A Function Call

Imagine you have a JavaScript function that makes a request to your server. Perhaps it is sending user input from a textarea to be processed by the server. You may want to wrap this function in a keyboard event listener so that you are sure to react immediately to any user input. However, as the user starts typing away into this text area you may find that way to many requests are being fired off to the server. The request needs to be throttled.

You can roll your own approach to sufficiently intermittent server calls. Though, it turns out that underscore.js comes with two functions out of the box for this kind of behavior.

  • throttle will give you a function that wraps your function in a way that essentially rate-limits it to being called at most once every N milliseconds.

  • debounce, on the other hand, will give you a function that only calls your function once N milliseconds has passed since it was last called.

These are two subtly different approaches to making sure a function gets called, just not too often.

h/t Jake Worth

Character Codes from Keyboard Listeners

If I create the following keyboard event listeners for keydown, keypress, and keyup:

window.addEventListener('keydown', function(e) { console.log("Keydown: " + e.charCode + ", " + e.keyCode); });
window.addEventListener('keypress', function(e) { console.log("Keypress: " + e.charCode + ", " + e.keyCode); });
window.addEventListener('keyup', function(e) { console.log("Keyup: " + e.charCode + ", " + e.keyCode); });

and then I press A, my browser console will read the following:

Keydown: 0, 65
Keypress: 65, 65
Keyup: 0, 65

and if I then press a, my browser console will read:

Keydown: 0, 65
Keypress: 97, 97
Keyup: 0, 65

The keypress event seems to be the way to go. Regardless, there seems to be quite a bit of incompatibility and lack of support across browsers for various aspects of the keyboard events.