Profile picture component
Published: 2022-10-23 21:46:01

Hi! I want to share a component of the profile picture. 
At the first glance, there is nothing special, but…  we're going to do a lettered avatar, because not all users upload their profile picture, so, we need a placeholder.
I think you're familiar with this "lettered avatar" concept

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nfwuwtl9pml21s6r22u.png)

As an example, I found these packages
- https://github.com/ipavlyukov/react-lettered-avatar
- https://github.com/hammadhere7/lettered-avatar#readme

But our component is more specific, and I don't see any need to use external libraries, because the component is pretty simple. So let's break it down.

---

## The design

The design is pretty simple. There will be two states and another one `meta` when the image is loading. This is how they look like:

![design preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6cd5iyva0n77gz1qkxzf.gif)

---

## Step 1. Image as a `background-image`

This is our step one we need to add two props here — first is the `className` we will manage the size of the component with this prop, and the second is `image`. I added it as a `background-image` and not as `background` because I manage other background props in component's `css`. There will be enough `background-size: cover` and `background-position: center`  

```tsx
// ProfilePic/index.tsx
import React from "react";
import styles from "./styles.module.css";

/////////////////////////////////
//////// TYPES AND PROPS ////////
/////////////////////////////////

export interface Props {
  className?: string;
  image?: string;
}

///////////////////////////////
///////// COMPOENENT //////////
///////////////////////////////

const ProfilePic: React.FC<Props> = (props) => {
  return (
    <div
      className={`${styles.avatar} ${props.className ? props.className : ""}`}
      style={{
        backgroundImage: `url(${props.image})`
      }}
    > 
    </div>
  );
};

ProfilePic.defaultProps = {
  className: "",
  image: ""
} as Partial<Props>;

export default ProfilePic;

```

---

## Step 2. Generate random gradients

Oh, there are so many ways how to generate gradients. In my taste the best way is to take two primary colors from the picture when you upload it to a server, … but we don't have this opportunity, so we will use predefined pairs. Also, there is [another way to generate fully random colors](https://stackoverflow.com/a/21682946/6149185)

**There is one important condition for gradients** — the gradient should always be the same for each user, but we can't store gradients in a DB, because we have old users as well, they should have the same functionality as new users. We can solve this by generating a hash from the user's name and then convert it into a color.


This is our function to make the gradient, basically the function returns one gradient pair from the pairs array. This approach has one disadvantage — the more users in a row you have, the more gradient pairs you need, otherwise you will see gradient duplicates. 


![gradients preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e6rpbwhspwm52wkp7e8i.png)

```ts
// stringToGradient.ts
const gradients = [
  ["#FBB199", "#FFD12E"],
  ["#E2ECA6", "#77A3FA"],
  ["#A7A0EF", "#77A3FA"],
  ["#94BE39", "#FFF3B4"],
  ["#FFA4A4", "#737ABB"]
];

const stringToGradient = (string: string) => {
  // trim the string, remove all spaces
  const trimmedString = string.trim().replace(/\s/g, "");
 
  // this is how we take the first letter. It works with emojies.
  const startHash = trimmedString
    .split("")
    .reduce((acc, char) => acc + char.charCodeAt(0), 0);

  // Covert the hash number into the number we can
  const gradient = startHash % gradients.length;

  // and return the linear-gradient
  return `linear-gradient(45deg, ${gradients[gradient][0]} 15%, ${gradients[gradient][1]} 90%)`;
};

export default stringToGradient;
``` 

We will add this function as the second image background and if there is no image, or it's still loading, a user will see the gradient.

```tsx
// ProfilePic/index.tsx
import React from "react";
import stringToGradient from "./stringToGradient";
import styles from "./styles.module.css";

/////////////////////////////////
//////// TYPES AND PROPS ////////
/////////////////////////////////

export interface Props {
  className?: string;
  name: string; // pass it into `stringToGradient`
  image?: string;
}

///////////////////////////////
///////// COMPOENENT //////////
///////////////////////////////

const ProfilePic: React.FC<Props> = (props) => {
  //
  return (
    <div
      className={`${styles.avatar} ${props.className ? props.className : ""}`}
      style={{
        backgroundImage: `url(${props.image}), ${stringToGradient(props.name)}`
      }}
    >
    </div>
  );
};

ProfilePic.defaultProps = {
  className: "",
  image: ""
} as Partial<Props>;

export default ProfilePic;
```

---

## Step 3. Add a letter

There are some technics where devs generate letters with `canvas` and then convert them into images. But I'm not the fan of the approach, because it's not scalable. You need to generate several images with the letter for different sizes or one universal, that can be heavy.

I think it's better to use vector — we can scale it, it has a small weight, and it's easier to maintain.

Here how we will do this. We can't use usual tags like `span`, `p`, because you can't scale them if you set the avatar size from `60px` to `120px`, your `font-size: 16px` remains the same. But SVG `text` tag can handle the magic of scaling. You set the default `font-size` and then it will be scaled depending on the size of the parent, just set the size of SVG `100%`. 

We will also need to align the text to the center. Just add `textAnchor="middle"` and `alignmentBaseline="middle"` to the `text` and set the `y` and `x`. `x,y` params can be relative from font to font, but for me the ideal center is `x="50%"` and `y="52%"`.


```tsx
// ProfilePic/index.tsx
import React from "react";
import stringToGradient from "./stringToGradient";
import styles from "./styles.module.css";

/////////////////////////////////
//////// TYPES AND PROPS ////////
/////////////////////////////////

export interface Props {
  className?: string;
  name: string;
  image?: string;
}

///////////////////////////////
///////// COMPOENENT //////////
///////////////////////////////

const ProfilePic: React.FC<Props> = (props) => {
  //
  return (
    <div
      className={`${styles.avatar} ${props.className ? props.className : ""}`}
      style={{
        backgroundImage: `url(${props.image}), ${stringToGradient(props.name)}`
      }}
    >
      {!props.image && (
        <svg className={styles.letterPlaceholder} viewBox="0 0 60 60">
          <text x="50%" y="52%" textAnchor="middle" alignmentBaseline="middle">
            {Array.from(props.name)[0].toUpperCase()}
          </text>
        </svg>
      )}
    </div>
  );
};

ProfilePic.defaultProps = {
  className: "",
  image: ""
} as Partial<Props>;

export default ProfilePic;

```
And the final result 👉 [codesandbox](https://codesandbox.io/s/profile-picture-component-ftijqf?file=/src/ProfilePic/index.tsx)

{% embed https://codesandbox.io/embed/profile-picture-component-ftijqf?fontsize=14&hidenavigation=1&theme=dark %}