Lodash get() does not default for value null
It's been 1+ years of using lodash. I have no complaints except for it being too large on the bundle.
However, recently I have found an issue that I thought was handled by lodash.get()
, but it is not. ๐ซ
The Story
_.get()
is the most used utility function in my code. Recently the following lines of code broke in quite a few places.
const todos = get(apiData, ["data", "todos"], []);
const updatedTodos = todos.map((todo) => ({ ...todo, key: todo.id }));
Debug
So why is the above code breaking? ๐
To understand this let's read the code.
On Line-1 we are saying get
apiData.data.todos
if not assign[]
to todos.On Line-2 we are simply looping over the array from the previous line to add a property
key
.
Everything sounds right?
But here is the catch! ๐
WE ARE TRUSTING LINE 1 TO RETURN AN ARRAY!
.map()
can only be used on Array. So the code breaks on Line-2 if todos
is not an array.
Essentially my code broke for todos
being null
in some cases.
How can this be possible? We are defaulting todos
to []
if we don't find apiData.data.todos
. Seems handled right?
No! we are mistaking null
for undefined
.
Didn't find the expected key technically === undefined
in Javascript.
So lodash get()
takes the default value only if it encounters undefined
in the given path (here in this example: apiData.data.todos
) but not for any value else ( null
)
Lodash mentions the same in its docs.
It is now clear that lodash does not take the default value if the end value is null
.
Now! How to fix this? ๐ค
The Fix
The fix is simple.
We can use optional chaining
todos?.map
on Line-2.const updatedTodos = todos?.map((todo) => ({ ...todo, key: todo.id }));
OR
We can assign
[]
to todos, if the value isnull
on Line-1.const todos = get(apiData, ["data", "todos"], []) ?? [];
There is a downside to Fix-1.
updatedTodos
will be undefined
if todos is nullish ( undefined
or null
) which is not intended at all times. Consider a situation where you are looping updatedTodos
somewhere down in the code. Then the same issue arises.
Always meet the type expectations. If your Line-2 expects an array, Line-1 should take care of it in any case.
Using typescript would help in these cases. But lodash.get() cannot infer value type so you need to handle it explicitly.
So we are going ahead with Fix-2. But do you see the repeated []
?
Let's refactor โป
๐ The Nullish coalescing operator (??) returns the right-hand side operand when its left-hand side operand is null
or undefined
.
const todos = get(apiData, ["data", "todos"]) ?? [];
The Solution
The above fix works perfectly fine. โ
But it is not scalable. ๐
In the above code, we need to add ?? []
everywhere we need a default value and the same applies to similar use cases like using a default value for an Empty string ""
and we also need to repeat this where ever required.
If something is repeated in code, then it can be refactored.
Let's create a utility function that takes care of this.
Design: get(object, path, defaultValue)
Functionality: returns defaultValue if the value turns out nullish
else returns the value.
// utility.ts
import lodashGet from "lodash/get";
type TGetParams = Parameters<typeof lodashGet>;
export function get(
object: TGetParams[0],
path: TGetParams[1],
defaultValue?: TGetParams[2]
) {
const value = lodashGet(object, path);
if (defaultValue !== undefined) {
return value ?? defaultValue;
}
return value;
}
We can now use this new get() function in your code if you plan to use the default value for nullish
values. ๐
You can rename this function to getNonNullish()
or lodash get function to lodashGet()
to avoid using them interchangeably.
PS: Suggest an alternate name for getNonNullish
based on its functionality in the comments. ๐
Subscribe to my newsletter
Read articles from Anjan Talatam directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by