Default parameters and nulls in ES6

Ben Brostoff
3 min readJun 30, 2018

--

Parameter defaults are only impactful for undefined in ES6 JS. The MDN docs are clear as day here

Default function parameters allow formal parameters to be initialized with default values if no value or undefined is passed.

…yet I have still been burned at least a few times on not appreciating that null is going to be handled differently than undefined. Consider the code below:

const computeTotal = ({ parts = [] } = {}) => (
parts.reduce((total, part) => total + part, 0)
);

This function seems to be alright:

computeTotal({ parts: [1, 2] }); // 3
computeTotal({}); // 0
computeTotal(); // 0

Yet it completely fails given a null parts .

computeTotal({ parts: null });> TypeError: Cannot read property 'reduce' of null

parts could be null in a range of situations. One easy example — a row in a database with a null column value.

computeTotal needs to handle null , and defaulting parameters is useless since it only handles undefined. I think defaulting the parameter AND handling null in the body of the function — while it solves the problem — is possibly confusing to future code readers:

const computeTotal = ({ parts = [] } = {}) => (
(parts || []).reduce((total, part) => total + part, 0)
);

It would be easy to think parts || [] was some leftover cruft from a refactor or redundant, even though it has a different effect — parts || []protects against all falsey values and not just undefined. Similarly

const computeTotal = ({ parts } = {}) => (
(parts || []).reduce((total, part) => total + part, 0)
);

might cause a future developer to refactor the function to the failing function for the same reason. So what is the right way to do this?

My first recommendation would be to always have unit tests that test the null scenario. This will at least stop others from making refactors that break the code.

My second recommendation would be to use a more verbose check for null or undefined values, and stop protecting against false, empty strings and NaNs . If parts is any of these values, something is likely wrong with the data and I want to know about it (note that this whole example operates on the assumption that computeTotal takes an object returned by a database driver). So, I’m in favor of:

const computeTotal = ({ parts } = {}) => (
(parts != null ? parts : []).reduce(
(total, part) => total + part, 0
)
);

At least in this example, because a null or undefined parts will always return 0 for the total, this really should be changed to:

const computeTotal = ({ parts } = {}) => (
parts != null ? parts.reduce((total, part) => total + part, 0) : 0
);

Finally, if handling all falsey values is important, I think at least a comment, some type checking or really any signaling within the code about possible values for parts makes sense.

Like this post? Check out my blog, subscribe to my reading recommendation newsletter or let’s get in touch about consulting, pair programming, or anything tech-related on your mind.

--

--