[JavaScript Series 14] Design reusable UI patterns and render functions

한국어 버전

Once you understand accessibility and state flow, bundle repeating UI into patterns to reduce duplication. A pattern is “a render function that receives state and updates the DOM”.

Key terms

  1. Pattern catalog: the documented list of reusable UI fragments—cards, lists, banners—that details the required state, events, and accessibility hooks.
  2. Partial update: a performance-friendly strategy that replaces only the changed item instead of rerendering entire lists.
  3. dataset: a property for reading metadata such as data-id via element.dataset.id.
  4. Template helper: a function that bundles shared render rules (mounting, event wiring, cleanup) to reduce repetition.

Core ideas

  • Render function: a pure mapping from state to HTML or DOM operations; identical input must yield identical output.
  • Pattern catalog: enumerate repeated structures (cards, lists, banners, modals) and record their state needs, events, and accessibility attributes.
  • Partial update: update only the affected list item with an identifier, typically using dataset or id attributes.
  • View/state separation: keep business logic outside render functions so maintenance stays easy.

Card and list patterns

Define cards and lists as pure functions first.

function renderCard({ title, summary, actions = [] }) {
  return `
    <article class="card">
      <h3>${title}</h3>
      <p>${summary}</p>
      <div class="card-actions">
        ${actions.map((action) => `<button data-action="${action.id}">${action.label}</button>`).join("")}
      </div>
    </article>
  `;
}

function renderList({ items, emptyMessage = "No items yet." }) {
  if (items.length === 0) {
    return `<p class="list-empty" role="status">${emptyMessage}</p>`;
  }
  return `
    <ul class="list" role="list">
      ${items.map((item) => `<li data-id="${item.id}">${renderCard(item)}</li>`).join("")}
    </ul>
  `;
}
const listRoot = document.querySelector("#project-list");

function updateProjectList(projects) {
  if (!listRoot) return;
  listRoot.innerHTML = renderList({ items: projects });
}

function updateSingleProject(project) {
  const target = listRoot?.querySelector(`[data-id="${project.id}"]`);
  if (!target) return;
  target.outerHTML = `<li data-id="${project.id}">${renderCard(project)}</li>`;
}

updateProjectList re-renders everything, while updateSingleProject powers partial updates.

Building our reusable UI helper

Now create a helper that unifies template rendering and mounting rules.

function createPatternRenderer({ root, template, onMount }) {
  let currentState = null;

  function render(state) {
    currentState = state;
    root.innerHTML = template(state);
    onMount?.({ root, state: currentState });
  }

  return { render, getState: () => currentState };
}

const banner = createPatternRenderer({
  root: document.querySelector(".alert-banner"),
  template: ({ type, message }) => `
    <div class="alert alert--${type}" role="alert">
      ${message}
      <button class="alert-close" aria-label="Close banner">×</button>
    </div>
  `,
  onMount: ({ root }) => {
    root.querySelector(".alert-close")?.addEventListener("click", () => {
      root.innerHTML = "";
    });
  },
});

banner.render({ type: "info", message: "Save completed." });

Why it matters

  • Splitting code by pattern gives teammates a shared language: “this screen uses the renderList pattern”.
  • Partial updates protect performance when data volumes grow.
  • A pattern catalog sets the foundation for a design system where Figma components and code share the same names.

Practice

  • Follow along: implement card, list, and banner patterns as render functions, feeding them the same state to compare outputs.
  • Extend: build a helper like createPatternRenderer so every pattern follows the same render + onMount lifecycle.
  • Debug: intentionally mutate state inside a render function, observe the bug, and revert to a pure function.
  • Done when: at least three patterns accept the same state shape, one of them supports partial updates, and everything renders correctly.

Wrap-up

Reusable patterns speed up UI expansion. Next we will design API error states and network messaging on top of these patterns.

💬 댓글

이 글에 대한 의견을 남겨주세요