How to use tagged templates - kdaisho/Blog GitHub Wiki

You might have seen something like this;

const Button = styled.button`
  background: ${props => props.primary ? 'blue' : 'gray'};
  color: #fff;
  padding: 1rem;
`;

That can be done with tagged templates syntax. It's similar to a function declaration, but it comes without parenthesis.

function format(strings, ...values) {
  let output = "";
  for (let i = 0; i < strings.length; i++) {
    output += strings[i]
    if (i < values.length) {
      if (typeof values[i] === 'number') {
        output += `<i>${values[i]}</i>`
      } else {
        output += `<b>${values[i]}</b>`
      }
    }
  }

  return output
}

let name = 'Maria'
let age = 30

format`Data: ${name} has been working since she was ${age}!`
// 'Data: <b>Maria</b> has been working since she was <i>30</i>!'

If you want to escape apostrophe ' by doubling it '';

function sql(strings, ...values) {
  return strings.reduce((result, string, i) => {
    let value = values[i - 1];

    if (typeof value === 'string') {
      value = `'${value.replace(/'/g, "''")}'`; // Escape single quotes
    }
    return result + value + string;
  });
}

const table = 'users';
const column = 'name';
const value = "O'Reilly";

sql`SELECT * FROM ${table} WHERE ${column} = ${value}`;
// SELECT * FROM 'users' WHERE 'name' = 'O''Reilly'

More use cases!

User comes with different set of properties, and I want to display name if available.

type User = {
  name?: string
  email: string
  phone?: string
}

const user1: User = {
  email: '[email protected]',
}

const user2: User = {
  name: 'Daisho Komiyama',
  email: '[email protected]',
}

const user3: User = {
  name: 'Jason Smith',
  email: '[email protected]',
  phone: '123-456-7890',
}

const user4: User = {
  email: '[email protected]',
  phone: '911',
}

function label(strings: TemplateStringsArray, ...values: User[]) {
  let output = ''

  for (let i = 0; i < strings.length; i++) {
    output += strings[i]

    if (values[i]) {
      if (values[i].name) {
        output += values[i].name
      } else {
        output += values[i].email
      }

      if (values[i].phone) {
        output += `, phone: ${values[i].phone}`
      }
    }
  }

  return output
}

console.log(label`Our primary customer is ${user1} and all good.`)
// Our primary customer is someone@gmail.com and all good.
console.log(label`Our primary customer is ${user2} and all good.`)
// Our primary customer is Daisho Komiyama and all good.
console.log(label`Our primary customer is ${user3} and all good.`)
// Our primary customer is Jason Smith, phone: 123-456-7890 and all good.
console.log(label`Our primary customer is ${user4} and all good.`)
// Our primary customer is spvm@gmail.com, phone: 911 and all good.

If you want to do above without tagged template, you would end up with a lot of ?. and ?? operators just like below examples.

console.log(`Our primary customer is ${user1?.name ?? user1.email}${user1?.phone ? `, phone: ${user1?.phone}` : ''} and all good.`)
console.log(`Our primary customer is ${user2?.name ?? user2.email}${user2?.phone ? `, phone: ${user2?.phone}` : ''} and all good.`)
console.log(`Our primary customer is ${user3?.name ?? user3.email}${user3?.phone ? `, phone: ${user3?.phone}` : ''} and all good.`)
console.log(`Our primary customer is ${user4?.name ?? user4.email}${user4?.phone ? `, phone: ${user4?.phone}` : ''} and all good.`)

It's not only hard to read but also error-prone. Tagged template is a great way to handle this kind of situation.

More examples!

I don't care if it's Date or string.

function incidentTag(strings: TemplateStringsArray, ...values: Date[] | string[]) {
  const outputArray = values.map(
    (value, i) =>
    `${strings[i]}${
      value instanceof Date
        ? value.toLocaleString('en-US', {
          month: 'short',
          day: 'numeric',
          year: 'numeric',
        })
        : value
    }`
  )

  return outputArray.join('') + strings[strings.length - 1]
}

const date1 = new Date('Aug 14, 2002')
const date2 = 'Dec 25, 1977'

console.log(incidentTag`The incident occurred on ${date1}.`)
// The incident occurred on Aug 14, 2002.
console.log(incidentTag`The incident occurred on ${date2}.`)
// The incident occurred on Dec 25, 1977.
enum UserStatus = {
  INACTIVE = 0,
  ACTIVE = 1,
  ARCHIVED = 2,
}

export function tag(
  strings: TemplateStringsArray,
  ...placeholders: (Record<string, string | UserStatus> | null)[]
): string {
  return strings.reduce((acc, str, i) => {
      acc += str;
      if (typeof placeholders[i] === 'object') { // null is also 'object'
          acc += placeholders[i]?.name ?? placeholders[i]?.email ?? 'an user';
      }
      return acc;
  }, '');
}
⚠️ **GitHub.com Fallback** ⚠️