Skip to main content
Version: 1.2.0-beta1

Clearable Persisted Values

Some fields need three distinct meanings:

  • a real value is present
  • the user explicitly cleared the value
  • no value has ever been stored, so the app should use a default

That is where null matters.

The key semantic rule

react-mnemonic gives you three different behaviors:

ActionWhat it doesWhat happens after reload
set(null)Persists an explicit cleared valueStays cleared
remove()Deletes the key entirelyFalls back to defaultValue
reset()Persists defaultValueRehydrates the default

If your UI has a real "clear" action and that cleared state should survive reload, model the persisted value as nullable and write null.

Interactive example

Clearable persisted field

null stays cleared

This field persists string | null. Empty input is normalized to null, which records a real clear intent instead of deleting the key.

Reload behavior: after set(null), the field stays cleared. After remove() or reset(), the default Anonymous comes back.
{
  "displayName": "Anonymous",
  "semanticState": "Value equals default"
}

Clear the field, reload the page, and notice the difference:

  • set(null) keeps the field cleared
  • remove() brings the default back
  • reset() also brings the default back

Canonical pattern

import { useMnemonicKey } from "react-mnemonic";

function DisplayNameField() {
const {
value: displayName,
set,
remove,
reset,
} = useMnemonicKey<string | null>("displayName", {
defaultValue: "Anonymous",
});

return (
<>
<input
value={displayName ?? ""}
onChange={(e) => {
const nextValue = e.target.value.trim();
set(nextValue === "" ? null : nextValue);
}}
/>

<button onClick={() => set(null)}>Clear and keep cleared</button>
<button onClick={() => remove()}>Remove key</button>
<button onClick={() => reset()}>Reset default</button>
</>
);
}

The important part is the normalization step:

const nextValue = e.target.value.trim();
set(nextValue === "" ? null : nextValue);

That turns "empty user input" into "persisted clear intent".

undefined vs null

When a field is clearable and persisted, prefer:

  • null for "the user explicitly cleared this"
  • undefined for temporary in-memory absence before normalization

Before writing, normalize undefined to null.

const normalizeBeforePersist = <T,>(value: T | null | undefined) => value ?? null;

This avoids ambiguous behavior where a caller thinks they cleared a value but actually removed the key or let a default come back later.

Wrapper example

If your app has many fields like this, wrap the pattern locally:

import { useMnemonicKey } from "react-mnemonic";
import type { UseMnemonicKeyOptions } from "react-mnemonic";

function useClearableMnemonicKey<T>(key: string, options: UseMnemonicKeyOptions<T | null>) {
const field = useMnemonicKey<T | null>(key, options);

return {
...field,
set: (next: T | null | undefined | ((current: T | null) => T | null | undefined)) =>
field.set((current) => {
const resolved = typeof next === "function" ? next(current) : next;
return resolved ?? null;
}),
clear: () => field.set(null),
};
}

This is app-level convenience code, not a new library API. It is useful when:

  • your forms frequently map empty strings to persisted null
  • you want one standard clear() action everywhere
  • you want to prevent accidental undefined writes from creeping in

Nullable schema guidance

If the key is schema-managed, the schema must also allow null:

{
type: ["string", "null"];
}

Without that, a clear action that writes null will fail validation.

When to use remove()

remove() is still correct when you want "forget this key and go back to first-load defaults".

Examples:

  • dismissing a one-off tutorial so a future rollout can re-seed defaults
  • clearing a cached search result object that should be recomputed
  • removing stale rollout state during recovery flows

Use remove() when you want absence. Use null when you want durable cleared intent.