Snippets

Flattened Object to Merged

Utility functions to convert flattened dot-notation objects to nested objects

TS

/**
 * Creates a key in a dot-notation object from the
 * given separator and a list of arguments.
 */
function dotter(sep: string) {
  return function dot(...args: string[]) {
    return args.join(sep);
  };
}

/**
 * Transforms a dot-notation object to an nested object
 * with the given dot character.
 */
function merger(sep: string) {
  return function merge(obj: Record<string, unknown>) {
    const result: Record<string, any> = {};

    Object.keys(obj).forEach((key) => {
      const path = key.split(sep);
      path.reduce((acc, curr, idx) => {
        const isEnd = idx === path.length - 1;
        acc[curr] = isEnd ? obj[key] : acc[curr] || {};
        return acc[curr];
      }, result);
    });

    return result;
  };
}

/**
 * Makes a dot function and a merge function given the dot character.
 */
export function createDotter(sep = ".") {
  return {
    dot: dotter(sep),
    merge: merger(sep),
  };
}
/**
 * Creates a key in a dot-notation object from the
 * given separator and a list of arguments.
 */
function dotter(sep: string) {
  return function dot(...args: string[]) {
    return args.join(sep);
  };
}

/**
 * Transforms a dot-notation object to an nested object
 * with the given dot character.
 */
function merger(sep: string) {
  return function merge(obj: Record<string, unknown>) {
    const result: Record<string, any> = {};

    Object.keys(obj).forEach((key) => {
      const path = key.split(sep);
      path.reduce((acc, curr, idx) => {
        const isEnd = idx === path.length - 1;
        acc[curr] = isEnd ? obj[key] : acc[curr] || {};
        return acc[curr];
      }, result);
    });

    return result;
  };
}

/**
 * Makes a dot function and a merge function given the dot character.
 */
export function createDotter(sep = ".") {
  return {
    dot: dotter(sep),
    merge: merger(sep),
  };
}

Examples

LazyForm.tsx

A lazy form component that extracts and merges form data directly from the DOM. See the live demo in the next section 👇

TSX

"use client";
import { CodeHighlighter } from "components/CodeHighlighter";
import { useRef, useState } from "react";
import { createDotter } from "./dot-to-merge";
import styles from "./styles.module.css";
import { useIds } from "./useIds";

const { dot, merge } = createDotter(".");

export const LazyForm = () => {
  const {
    ids: todoIds,
    addId: addTodoId,
    removeId: removeTodoId,
    resetIds: resetTodoIds,
  } = useIds();
  const [result, setResult] = useState<any>({});

  const formRef = useRef<HTMLFormElement>(null);

  const updateResult = () => {
    if (!formRef.current) return;
    const formData = new FormData(formRef.current);
    const flatObject = Object.fromEntries(formData.entries());
    const data = merge(flatObject);
    setResult(data);
  };

  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    updateResult();
  };

  const onReset = () => {
    setResult({});
    resetTodoIds();
  };

  return (
    <div className={styles.top}>
      <p>
        This form uses dotted names such as: <code>user.username</code>,{" "}
        <code>user.email</code>, etc. Fill the form then click Submit or press
        Enter to see the parsed and merged form data in &quot;Result&quot;.
        <br />
      </p>
      <div className={styles.container}>
        <form
          ref={formRef}
          className={styles.form}
          onSubmit={onSubmit}
          onReset={onReset}
        >
          <div>
            <label>Username</label>
            <input type="text" name={dot("user", "username")} />
          </div>
          <div>
            <label>Email</label>
            <input type="email" name={dot("user", "email")} />
          </div>

          <h4>ToDos</h4>
          {todoIds.map((id) => {
            return (
              <div className={styles.row} key={id}>
                <label>#{id}&nbsp;Title</label>
                <input
                  type="text"
                  name={dot("todos", id.toString(), "title")}
                />
                <button type="button" onClick={() => removeTodoId(id)}>
                  x
                </button>
              </div>
            );
          })}
          <button type="button" onClick={addTodoId}>
            Add ToDo
          </button>

          <div className={styles.actions}>
            <button type="submit">Submit</button>
            <button type="reset">Reset</button>
          </div>
        </form>

        <div>
          <h4>Merged Form Result</h4>
          <CodeHighlighter
            language="json"
            tag="JSON"
            content={JSON.stringify(result, null, 2)}
            copy={false}
          />
        </div>
      </div>
      <p>
        Tip: This can be used in conjunction with server-side validators (e.g{" "}
        <code>zod</code>) to ensure valid form data is supplied to the server.
      </p>
    </div>
  );
};
"use client";
import { CodeHighlighter } from "components/CodeHighlighter";
import { useRef, useState } from "react";
import { createDotter } from "./dot-to-merge";
import styles from "./styles.module.css";
import { useIds } from "./useIds";

const { dot, merge } = createDotter(".");

export const LazyForm = () => {
  const {
    ids: todoIds,
    addId: addTodoId,
    removeId: removeTodoId,
    resetIds: resetTodoIds,
  } = useIds();
  const [result, setResult] = useState<any>({});

  const formRef = useRef<HTMLFormElement>(null);

  const updateResult = () => {
    if (!formRef.current) return;
    const formData = new FormData(formRef.current);
    const flatObject = Object.fromEntries(formData.entries());
    const data = merge(flatObject);
    setResult(data);
  };

  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    updateResult();
  };

  const onReset = () => {
    setResult({});
    resetTodoIds();
  };

  return (
    <div className={styles.top}>
      <p>
        This form uses dotted names such as: <code>user.username</code>,{" "}
        <code>user.email</code>, etc. Fill the form then click Submit or press
        Enter to see the parsed and merged form data in &quot;Result&quot;.
        <br />
      </p>
      <div className={styles.container}>
        <form
          ref={formRef}
          className={styles.form}
          onSubmit={onSubmit}
          onReset={onReset}
        >
          <div>
            <label>Username</label>
            <input type="text" name={dot("user", "username")} />
          </div>
          <div>
            <label>Email</label>
            <input type="email" name={dot("user", "email")} />
          </div>

          <h4>ToDos</h4>
          {todoIds.map((id) => {
            return (
              <div className={styles.row} key={id}>
                <label>#{id}&nbsp;Title</label>
                <input
                  type="text"
                  name={dot("todos", id.toString(), "title")}
                />
                <button type="button" onClick={() => removeTodoId(id)}>
                  x
                </button>
              </div>
            );
          })}
          <button type="button" onClick={addTodoId}>
            Add ToDo
          </button>

          <div className={styles.actions}>
            <button type="submit">Submit</button>
            <button type="reset">Reset</button>
          </div>
        </form>

        <div>
          <h4>Merged Form Result</h4>
          <CodeHighlighter
            language="json"
            tag="JSON"
            content={JSON.stringify(result, null, 2)}
            copy={false}
          />
        </div>
      </div>
      <p>
        Tip: This can be used in conjunction with server-side validators (e.g{" "}
        <code>zod</code>) to ensure valid form data is supplied to the server.
      </p>
    </div>
  );
};

Demo - Lazy Form

This form uses dotted names such as: user.username, user.email, etc. Fill the form then click Submit or press Enter to see the parsed and merged form data in "Result".

ToDos

Merged Form Result

JSON

{}
{}

Tip: This can be used in conjunction with server-side validators (e.g zod) to ensure valid form data is supplied to the server.

© 2021-2024 Rexford Essilfie. All rights reserved.