React Router Tutorial Question (not Codecademy specific course)

I’m on the web dev learning journey. Most recently I’ve been learning React Router and I’m excited to be close to building my first full stack (MERN) apps! The vast majority of the tutorials I’ve done have been within the Codecademy website but I decided to go outside of it for React Router since the Codecademy version is out of date.

Anyhow, I’m quite confused on an aspect of this tutorial: Tutorial v6.6.2 | React Router. Specifically, I’m confused about code in the contacts.js file (essentially it’s emulating database queries by using localStorage…or rather a package called localForage which uses localStorage). Anyhow, here is this particular module:

import localforage from "localforage"; import { matchSorter } from "match-sorter"; import sortBy from "sort-by"; export async function getContacts(query) { await fakeNetwork(`getContacts:${query}`); let contacts = await localforage.getItem("contacts"); if (!contacts) contacts = []; if (query) { contacts = matchSorter(contacts, query, { keys: ["first", "last"] }); } return contacts.sort(sortBy("last", "createdAt")); } export async function createContact() { await fakeNetwork(); let id = Math.random().toString(36).substring(2, 9); let contact = { id, createdAt: Date.now() }; let contacts = await getContacts(); contacts.unshift(contact); await set(contacts); return contact; } export async function getContact(id) { await fakeNetwork(`contact:${id}`); let contacts = await localforage.getItem("contacts"); let contact = contacts.find(contact => contact.id === id); return contact ?? null; } export async function updateContact(id, updates) { await fakeNetwork(); let contacts = await localforage.getItem("contacts"); let contact = contacts.find(contact => contact.id === id); if (!contact) throw new Error("No contact found for", id); Object.assign(contact, updates); await set(contacts); return contact; } export async function deleteContact(id) { let contacts = await localforage.getItem("contacts"); let index = contacts.findIndex(contact => contact.id === id); if (index > -1) { contacts.splice(index, 1); await set(contacts); return true; } return false; } function set(contacts) { return localforage.setItem("contacts", contacts); } // fake a cache so we don't slow down stuff we've already seen let fakeCache = {}; async function fakeNetwork(key) { if (!key) { fakeCache = {}; } if (fakeCache[key]) { return; } fakeCache[key] = true; return new Promise(res => { setTimeout(res, Math.random() * 800); }); }

I might be missing something obvious but I don’t understand how the updateContact function is actually accomplishing anything that persists the updated data into local storage…For more context, here is the section of the tutorial in which updateContact is called: Tutorial v6.6.2 | React Router. From my perspective, the form data is submitted, the action function is called (editAction if you’re looking at main.jsx), we turn the form data into an object, we call updateContact, and we redirect, etc. The app does work, somehow, which leads me to believe I’m missing something, but what’s strange is inserting console.log(contacts) or even console.log(contact) before Object.assign results in the updated contact data actually being logged. Regardless, I’m confused because we never make an update to the actual contacts array, as far as I can tell, before calling the set function and the only update to the contact object should just be lost to the ether since it’s never persisted anywhere? Or am I totally missing something here?

Sorry that this is off-site, but I feel most comfortable asking this here since I’ve used Codecademy as my primary learning website. I would appreciate any help here but I understand if going off-site is necessary to get an answer (though unfortunately React Router doesn’t have any forums as far as I can tell).

export async function updateContact(id, updates) {
  await fakeNetwork();
  let contacts = await localforage.getItem("contacts");
  let contact = contacts.find(contact => contact.id === id);
  if (!contact) throw new Error("No contact found for", id);
  Object.assign(contact, updates);
  await set(contacts);
  return contact;
}

If I am misunderstanding your question, then you probably should share a sample of the kind of data that is being stored in contacts and updates.

But, as best as I can gather, your question is about updateContact and how it is updating contacts.

Here is my understanding of what is happening in updateContact:

  • Since the .find() method is being used on contacts, so I assume that contacts is an array of objects.

  • contact finds the first object in the array whose id matches the id passed to updateContact as an argument. If no id is matched, an error is thrown.

  • Since Object.assign() method is being used, so I assume that both contact and updates are objects. If we look at the documentation for Object.assign() (have a look at the first Demo example. The returnedTarget and target objects are the same object), no new object is being created. Instead, the target object is being mutated/modified.
    Specifically,

Object.assign(contact, updates)
  • updates object is left unchanged.

  • contact object is modified (new object isn’t created). If there are keys in contact which are absent in updates, then those key-value pairs remain unchanged. If there are keys present in both contact and updates, then the values of the key-value pairs in updates over-write the corresponding key-value pairs in contact. If there are keys in updates which are absent in contact, then these key-value pairs are added to contact.

A new object hasn’t been assigned to contact. The existing contact object has been modified. This contact object still exists in the contacts array.

let contact = contacts.find(contact => contact.id === id);

The .find() method doesn’t return a copy of the matched object. It returns a reference to the matched object which is then assigned to the variable contact. When we modify contact in updateContact, we are modifying the object in the contacts array. We aren’t creating a new object or working with a copy. We are working with the original object.

Consider the example of mutating/modifying versus creating new object:

// Example: Mutating/Modifying
let x = {a: 1, b: 33};
let y = x;

// We modify y, but y points to same object as x
y["b"] = 100;  

console.log(x); // Output: { a: 1, b: 100 }
console.log(y); // Output: { a: 1, b: 100 }
console.log(x === y); // Output: true

// Example: Creating new object
let x = {a: 1, b: 33};
let y = x;

// We have assigned new object to y, so x and y no longer point to same object
y = {a: 1, b: 100};  

console.log(x); // Output: { a: 1, b: 33 }
console.log(y); // Output: { a: 1, b: 100 }
console.log(x === y); // Output: false
1 Like

Thank you very much! You’re correct, it is an array of objects. I did not realize that Array.prototype.find() returned a reference to the object. I’ve since brushed up on memory management in Javascript/the subject of object references with some additional reading so I’m glad I asked the question and thankful for the answer.

1 Like