Coding Case Study: Initial Value
Samuel Rouse

Samuel Rouse @oculus42

About: Tech generalist specializing in UI development. UI Architect by day. Playing with JavaScript for over 25 years.

Location:
Maine
Joined:
May 10, 2022

Coding Case Study: Initial Value

Publish Date: May 23
0 0

The answer isn't always simple, even when the problem seems to be. Figuring out answers and alternatives is an important skill. Let's look at a small exercise. Think about your potential solution, and how it stands up to the different problems.

Initial Value

Sometimes we need a value more unique than null, or undefined, or false, or 0. What do we do then?

Definitely Unique

There are scenarios where we need to ensure a value represents something unique that no other value can, like an initial value in a data store or a hook. Once set, we should never be able to get that value again. With a generic utility we may not know what values it will accept, so that value can't be something you could set. How can we create our own uniqueness? A few ways, thanks to JavaScript's design.

Objects & Strict Equality

You've probably seen simple demonstrations of strict equality showing that two different objects in JavaScript are not the same.

[] === []; // false

/*
 * This uses parentheses because a line starting with a curly brace
 * is assumed to be a code block, not an object. It's the same 
 * reason we can't use object destructuring without parentheses
 * or a declaration like let or const.
 */
({}) === {}; // false
Enter fullscreen mode Exit fullscreen mode

This means we can create unique objects and test for them. If they are later replaced with a different value, they will not match, even if the new value is the same type or shape of object.

So let's make a simple demo, starting with the initial value example.

Initial Value: Attempt 1 - Foreshadowing Title 🤔

Because of strict equality, all we really need is a value that hangs around to be the default that isn't used for anything else, right?

Code

const INITIAL_VALUE = {};
export const makeValueStore = (value = INITIAL_VALUE) => ({
  get: () => value,
  set: (newValue) => {
    value = newValue;
    return value;
  },
  isInitial: () => value === INITIAL_VALUE,
});
Enter fullscreen mode Exit fullscreen mode

Testing

const data1 = makeValueStore();
const data2 = makeValueStore(false);

data1.isInitial(); // true
data2.isInitial(); // false
Enter fullscreen mode Exit fullscreen mode

Now we have this very simple function that sets an initial value, and we can check for it. Once it's set to something else, isInitial() is no longer true.

Mistakes Were Made

Well, until we misuse the data. In the simple example, INITIAL_VALUE is in the same file, so we could just access it directly.

data2.set(INITIAL_VALUE);
data2.isInitial(); // true
Enter fullscreen mode Exit fullscreen mode

But even if that were in another file and not directly accessible, we still have an initial value shared across each value store.

// Access the shared initial value from another store
data2.set(data1.get());
data2.isInitial(); // true;
Enter fullscreen mode Exit fullscreen mode

It turns out we didn't guarantee uniqueness across instances of the value store. So let's try again.

Initial Value: Attempt 2 – Probably Fine 🧐

We know that the initial value shouldn't be public, and it should be unique to each instance.

Requirements:

  • A data store should default to a unique initial value.
  • The initial value should be unique to each instance.
  • The initial value should not be publicly accessible.

Code

export const makeValueStore = (value) => {
  const INITIAL_VALUE = {};
  let internalValue = value ?? INITIAL_VALUE
  return {
    get: () => internalValue,
    set: (newValue) => {
      internalValue = newValue;
      return internalValue;
    },
    isInitial: () => internalValue === INITIAL_VALUE,
  };
};
Enter fullscreen mode Exit fullscreen mode

Testing

Let's prove that we can't "borrow" an initial value from another instance.

const data1 = makeValueStore();
const data2 = makeValueStore(false);

data1.isInitial(); // true
data2.isInitial(); // false

data2.set(data1.get());
data2.isInitial(); // false;
Enter fullscreen mode Exit fullscreen mode

More Mistakes

Even though we now have isolation between instances, we can still access the initial value from a store and set it back later.

const myCopyOfInitial = data1.get();

data1.set('something else');
data1.isInitial(); // false

data1.set(myCopyOfInitial);
data1.isInitial(); // true;
Enter fullscreen mode Exit fullscreen mode

So we have to be mindful that to guarantee uniqueness, we cannot return the private value either.

Initial Value: Attempt 3 – This Time For Sure 👍

I think we're getting close!

Requirements:

  • A data store should default to a unique initial value.
  • The initial value should be unique to each instance.
  • The initial value should not be publicly accessible.
  • get() should not return the actual initial value.

Code

export const makeValueStore = (value) => {
  const INITIAL_VALUE = {};
  let internalValue = value ?? INITIAL_VALUE
  return {
    get: () => internalValue === INITIAL_VALUE ? null : internalValue,
    set: (newValue) => {
      internalValue = newValue;
      return internalValue;
    },
    isInitial: () => internalValue === INITIAL_VALUE,
  };
};
Enter fullscreen mode Exit fullscreen mode

Testing

const data1 = makeValueStore();

const initial = data1.get();
data1.isInitial(); // true

// Change it.
data1.set('later');
data1.isInitial(); // false

// Try to reset back to initial
data1.set(initial);
data1.isInitial(); // false
Enter fullscreen mode Exit fullscreen mode

We now have guaranteed uniqueness for the initial value. We can't access initial, but we can guarantee it can never be reset back.

More Unique

We used a plain object, but it isn't the only thing that creates a unique instance. We can use objects, arrays, even functions. Any non-primitive type, including functions, can provide this for you.

However, there is a type specifically made for this: Symbol. Symbols are made to provide the uniqueness we have been squeezing out of other types. They handily work in this case, and in many others.

Code

We can just swap in Symbol for the object:

export const makeValueStore = (value) => {
  const INITIAL_VALUE = Symbol('initial');
  let internalValue = value ?? INITIAL_VALUE
  return {
    get: () => internalValue === INITIAL_VALUE ? null : internalValue,
    set: (newValue) => {
      internalValue = newValue;
      return internalValue;
    },
    isInitial: () => internalValue === INITIAL_VALUE,
  };
};
Enter fullscreen mode Exit fullscreen mode

Tangent: Alternate Design

Once we decided we to not expose the INITIAL_VALUE, a different possibility opened up. We can change our logic and allow any initial value to still indicate true, and eliminate the need for the special instance check.

Code

export const makeValueStore = (value) => {
  let isInitial = true;
  let internalValue = value;
  return {
    get: () => internalValue,
    set: (newValue) => {
      isInitial = false;
      internalValue = newValue;
      return internalValue;
    },
    isInitial: () => isInitial,
  };
};
Enter fullscreen mode Exit fullscreen mode

This design keeps initial as a Boolean that is only ever set to false. Now there is no uniqueness concern, and regardless of what the value is set to, isInitial() provides clarity.

const data1 = makeValueStore('one');

data1.get();       // 'one';
data1.isInitial(); // true

// Change the value, it is no longer initial
data1.set('two');
data1.isInitial(); // false

// Change it back? Still false
data1.set('one');
data1.isInitial(); // false
Enter fullscreen mode Exit fullscreen mode

Conclusion

Sometimes your first idea works out. Sometimes it should be discarded if you find a better solution. But identifying the defects and documenting the new requirements as they become clear helps you better understand the problem and ensure the code does what you expect.

What were the different solutions you came up with for the problem? Did they suffer from any of the defects we found? Are there more defects we need to worry about? Let us know in the comments!

Comments 0 total

    Add comment