Typescript Stuff I Often Use

3y ago

Narrow down string consts

// I normally used the following declaration and let typescript interfere myString: string[]
const myString = ['apple', 'banana', 'orange']

// But if you go with this instead
const myString = ['apple', 'banana', 'orange'] as const
// Tyescript now types myString as readonly ['apple', 'banana', 'orange']

// If you now want to use this
const myFunction = (arg: typeof myString[number]) = {}

Typeguard


interface MyType {
  foo: number
}

const isMyType = (arg: any): arg is MyType => {
  return arg.foo !== undefined
}

/**
 * Sample usage of the User Defined Type Guard
 */
function doStuff(arg: Foo | Bar) {
    if (isMyType(arg)) {
        console.log(arg.foo); // OK
        console.log(arg.bar); // Error!
    }
    else {
        console.log(arg.foo); // Error!
        console.log(arg.bar); // OK
    }
}

Let's start by loading all images within a folder:

const imagesDirectory = path.join(
  process.cwd(),
  `public/products/${product.id}`
)

// Wrap the readdir in a try-catch block, otherwise the build will fail because of a unhandled exception.
try {
  const productImagePaths = await fs.readdir(imagesDirectory)

  return {
    props: {
      images: productImagePaths.map((path, index) => ({
        path: `/products/${product.id}/${path}`,
      })),
    },
  }
} catch (error) {
  console.warn(
    `Image ${product.name} has no images under /public/product/[id]!`
  )
  return {
    props: {
      images: [],
  }
}

Part Two: Generate a BlueDataURL

The Nextjs Image Component provides a prop called blurDataURL to show a very blurry version of the image while the actual image is loading. To generate this URL I use Plaiceholder:

const imagesDirectory = path.join(
  process.cwd(),
  `public/products/${product.id}`
)

// Wrap the readdir in a try-catch block, otherwise the build will fail because of a unhandled exception.
try {
  const productImagePaths = await fs.readdir(imagesDirectory)

  /**
   * Create blurDataURLs (base64) as image placeholders
   */

  const blurDataURLs = await Promise.all(
    productImagePaths.map(async src => {
      const { base64 } = await getPlaiceholder(`/products/${product.id}/${src}`)
      return base64
    })
  ).then(values => values)

  return {
    props: {
      images: productImagePaths.map((path, index) => ({
        path: `/products/${product.id}/${path}`,
        blurDataURL: blurDataURLs[index],
      })),
    },
  }
} catch (error) {
  console.warn(
    `Image ${product.name} has no images under /public/product/[id]!`
  )
  return {
    props: {
      images: [],
    },
  }
}

Part Three: Make it look sexy

Now it's time to use the image source and blurDataURL with the Nextjs image component.

// Use whichever styling solution you want. But it is important to add 'transition' to the Image component
const AnimatedImage = styled(Image, {
  transition: ".3s",
});

const ProductPage = ({ images }) => {
      <ImageContainer>
        {images.length ? (
          <AnimatedImage
            src={images[0].path}
            layout="fill"
            objectFit="cover"
            alt={images[0].path}
            placeholder="blur"
            blurDataURL={images[0].blurDataURL}
          />
        ) : (
          <Image
            src={PlaceholderImage}
            layout="fill"
            objectFit="cover"
            alt="placeholder"
          />
        )}
      </ImageContainer>
  );
};

The important parts are:

  • placeholder need to be blur
  • blurDataURL needs to be the base64 string generated by plaiceholder

I use layout="fill", that's why the actual image size is not that important to me. If you need this data you can use sharp's metadata for this.