ML

How to Generate User Avatars in React

January 11th, 2021

MLJAFAAFSFVFAG

Avatars are a fantastic UI element for any web app that features a user system. They help brighten up the site, give users a sense of identity, and help the end-user distinguishes between individual users. It's one of the best bang-for-your-buck components out there, and it's definitely worth the (small) time investment.

What makes fantastic user avatars?

There are a three attributes that define fantastic user avatars:

  • Unique. There may be duplicates, but very rarely.
  • Persistent. Each user's avatar should look exactly the same indefinitely, even across sessions and devices.
  • Beautiful. User avatars are likely going to be plastered all over your app, they should look nice!

The plan

We won't be using localstorage or caching them server-side like other methods. We'll actually be creating a simple client-side hashing algorithm to handle uniqueness and persistence.

The guide

Before we get started, there are three packages you'll want. They aren't required, but they are packages that make my life easier, and I think you'll find use in them too.

  1. tailwindcss - a CSS utility library that's going to make styling this avatar a breeze.
  2. clsx - a small tool used to simplify expressions in the className prop. I find this tool very handy when paired with tailwindcss.
  3. lodash - a JavaScript utility library that will make some of the logic easier.

Step 1: Create a UserAvatar.js file

This file will house our UserAvatar component, throw in this code for a simple boilerplate:

1 import React from 'react'
2
3 const UserAvatar = () => {
4 return (
5 <span className="bg-blue-500 text-blue-100 rounded-full w-9 h-9 inline-flex items-center justify-center text-lg font-semibold select-none">
6 ML
7 </span>
8 )
9 }
10
11 export default UserAvatar

Pretty basic, just a few tailwindcss classes and we have a pretty nice looking avatar! Note: I won't be showing the file where I'm actually rendering the component, but I'd recommend rendering this component somewhere in your app and looking at the output as you follow along.

Step 2: Create different color variants

We'll want different color variants to help distinguish users from each other. For now, we'll just throw those variants into an array, and utilize clsx and lodash to pick one at random:

1 import React from 'react'
2 import clsx from 'clsx'
3 import _ from 'lodash'
4
5 const UserAvatar = () => {
6 return (
7 <span
8 className={clsx(
9 'rounded-full w-9 h-9 inline-flex items-center justify-center text-lg font-semibold select-none',
10 _.sample(colorVariants)
11 )}
12 >
13 ML
14 </span>
15 )
16 }
17
18 export default UserAvatar
19
20 const colorVariants = [
21 'bg-red-500 text-red-100',
22 'bg-blue-500 text-blue-100',
23 'bg-purple-500 text-purple-100',
24 'bg-green-500 text-green-100',
25 ]

We've created the colorVariants array below the component. This provides a nice newspaper code structure for the file. We've also removed bg-blue-500 text-blue-100 from the original className string and are now using lodash to pick a random set of colors from the color variants array. Every time you refresh the page, a new set of colors will be randomly selected.

Step 3: Making the avatar persistant

Here is where that hashing algorithm comes in. We'll generate a hash for the input, and map the hashed value to our colorVariants array. The input of a hash function will always map to the same output. Let's create our own function that does this:

1 import React from 'react'
2 import clsx from 'clsx'
3
4 const UserAvatar = ({ initials }) => {
5 return (
6 <span
7 className={clsx(
8 'rounded-full w-9 h-9 inline-flex items-center justify-center text-lg font-semibold select-none',
9 colorVariants[getColorValue(initials)]
10 )}
11 >
12 {initials}
13 </span>
14 )
15 }
16
17 export default UserAvatar
18
19 const colorVariants = [
20 'bg-red-500 text-red-100',
21 'bg-blue-500 text-blue-100',
22 'bg-purple-500 text-purple-100',
23 'bg-green-500 text-green-100',
24 ]
25
26 const getColorValue = (value) => {
27 const hashValue = value
28 // split the string into an array chars ("ML" => ["M", "L"])
29 .split('')
30 // convert each char into it's charCode value ("B" => 66)
31 .map((char) => +char.charCodeAt(0))
32 // add up all the values
33 .reduce((accumulator, currentValue) => (accumulator += currentValue))
34
35 // modulus the hashValue by colorVariants.length to get a random
36 // value between 0 and colorVariants.length - 1
37 return hashValue % colorVariants.length
38 }

We've accepted a new prop initials to see this in action. Our new getColorValue function generates a hash for the provided value and maps the output to an item in our colorVariants array. If you create a few of these components with different initials, you'll see that each has a random color assigned, and they persist across page refreshes.

Step 4: Improving uniqueness

Since the input of our hash function inside getColorValue is the initials, the same set of initials will always map to the same color. This isn't good, differentiating between two people with the same name/initials is one of the main reasons we want things color-coded. The fix for this is simple: we'll use the username of the user instead to generate the hashValue. We also probably don't have the initials of the user's name on hand, so we'll go ahead and dynamically generate those as well:

1 import React from 'react'
2 import clsx from 'clsx'
3 import _ from 'lodash'
4
5 const UserAvatar = ({ name, username }) => {
6 // separate the name out into words
7 const words = _.words(name)
8
9 return (
10 <span
11 className={clsx(
12 'rounded-full w-9 h-9 inline-flex items-center justify-center text-lg font-semibold select-none',
13 colorVariants[getColorValue(username)]
14 )}
15 >
16 {words[0][0].toUpperCase()}
17 {words[1]?.[0].toUpperCase()}
18 </span>
19 )
20 }
21
22 export default UserAvatar
23
24 const colorVariants = [
25 'bg-red-500 text-red-100',
26 'bg-blue-500 text-blue-100',
27 'bg-purple-500 text-purple-100',
28 'bg-green-500 text-green-100',
29 ]
30
31 const getColorValue = (value) => {
32 const hashValue = value
33 // split the string into an array chars ("ML" => ["M", "L"])
34 .split('')
35 // convert each char into it's charCode value ("B" => 66)
36 .map((char) => +char.charCodeAt(0))
37 // add up all the values
38 .reduce((accumulator, currentValue) => (accumulator += currentValue))
39
40 // modulus the hashValue by colorVariants.length to get a random
41 // value between 0 and colorVariants.length - 1
42 return hashValue % colorVariants.length
43 }

We're now generating the avatar color from the username, and dynamically generating the initials from the actual name of the user.

What next?

  • Convert the component to TypeScript. The tutorial is in JavaScript to keep things simple, but having static type checking is amazing.
  • Accept a size prop and dynamically change the size of the avatar based on the value. Be sure to include a default prop value.
  • Add support for custom avatar images, only use the generated variant as a backup.

Conclusion

We've successfully created user avatars the have all three attributes of fantastic user avatars! This was a cool little solution I had implemented in a work project recently and had thought it would be cool to share. It's one of the few times my algorithms class actually helped me in React 😅.