Customizing an avatar with React

Customizing an avatar with React

We'll create a react project to customize a profile avatar image. This avatar will be rendered as a card, and we will be able to customize some graphical aspects such as the pose and expressions with a basic form.We are going to use the avatar-generator API. This API receives query parameters to return a specific character. You can see the final result here.

Let's begin.

First, let's analyze the requirements to render an avatar. To request an avatar, we just need to get from this base url https://avataaars.io/?avatarStyle=Transparent then we send query parameters to set the avatar properties. For example:

  • To set the hair style: topType=value
  • To set the hair color: hairColor=value
  • To set the clothing type: clotheType=value
  • To set the eye type: eyeType=value
  • To set the mouth type: mouthType=value

Now, let's build our setting card.

First, we build our form.js component:

1import { useEffect, useState } from 'react';
2
3const Form = (props) => {
4
5  const [form, setForm] = useState({
6    topType: 'ShortHairShortFlat',
7    hairColor: 'Black',
8    eyeType: 'Default',
9    mouthType: 'Default',
10    clotheType: 'Hoodie',
11  });
12
13  return <p>form</p>;
14}
15
16export default Form;
17
18

As you can see in the code above, we define a state to manage the form values, but you could use useRef or a hook to manage them.

Every state key should correspond to the queryParams specifications for the avatar in order to match the values in advance.

Now, let's return the JSX code:

1  return (
2    <form className={styles.form}>
3      <div className={styles.group}>
4        <label>Hair</label>
5        <select 
6          className={styles.selector}
7          onChange={changeHandler} 
8          name='topType'
9          value={form.topType}
10        >
11          <option value='ShortHairTheCaesar'>Short</option>
12          <option value='ShortHairDreads01'>Medium</option>
13          <option value='LongHairBigHair'>Long</option>
14        </select>
15      </div>
16      <div className={styles.group}>
17        <label>Hair color</label>
18        <select 
19          className={styles.selector}
20          onChange={changeHandler} 
21          name='hairColor'
22          value={form.hairColor}
23          >
24          <option value='Black'>Black</option>
25          <option value='Blonde'>Blonde</option>
26          <option value='Red'>Red</option>
27        </select>
28      </div>
29      <div className={styles.group}>
30        <label>Eyes</label>
31        <select 
32          className={styles.selector}
33          onChange={changeHandler} 
34          name='eyeType'
35          value={form.eyeType}
36        >
37          <option value='Default'>Neutral</option>
38          <option value='Close'>Close</option>
39          <option value='Wink'>Wink</option>
40        </select>
41      </div>
42      <div className={styles.group}>
43        <label>Mouth</label>
44        <select 
45          className={styles.selector}
46          onChange={changeHandler} 
47          name='mouthType'
48          value={form.mouthType}
49        >
50          <option value='Smile'>Smile</option>
51          <option value='Serious'>Serious</option>
52          <option value='Default'>Neutral</option>
53        </select>
54      </div>
55      <div className={styles.group}>
56        <label>Clothes</label>
57        <select 
58          className={styles.selector}
59          onChange={changeHandler} 
60          name='clotheType'
61          value={form.clotheType}
62        >
63          <option value='Hoodie'>Hoodie</option>
64          <option value='ShirtCrewNeck'>Shirt</option>
65          <option value='BlazerSweater'>Blazer</option>
66        </select>
67      </div>
68    </form>
69  );
70

As you see, I'm repeating the select inputs and matching the values with the state keys. It's better to use an isolated component to manage the select functionality, but in this basic example it's OK.

Now, let's create the changeHandler method

1const changeHandler = (evt) => {
2  setForm(current => {
3    return {
4      ...current,
5      [evt.target.name]: evt.target.value
6    }
7  });
8};
9

The spread operator is used to update the specific key state but without deleting the other keys. This is an approach to managing forms in React

Finally, let's communicate these state changes to the parent component through props:

1useEffect(() => {
2  props.onUpdate(form);
3}, [form, props]);
4

We need to use useEffect hook to emit every state change.

Now let´s work on the main avatar.js component:

1import { useState } from 'react';
2import styles from './avatar.module.css';
3import Form from './form';
4
5const Avatar = () => {
6
7  const updateAvatarHandler = (queries) => {
8    // set avatar
9  }
10
11  return (
12    <div className={styles.card}>
13      <img 
14        className={styles.image}
15        alt='avatar'
16        src=''
17      />
18      <Form 
19        onUpdate={updateAvatarHandler}
20      />
21    </div>
22  );
23};
24
25export default Avatar;
26

This component is pretty simple. We just rendered a card with an image tag and the form.js component. So far, it looks like this:

Finally, let's add the logic to render the avatar.

First, let's add an initial avatar, creating a default url:

1const defaultAvatar = `https://avataaars.io/?avatarStyle=Transparent&topType=ShortHairShortFlat&hairColor=Black&clotheType=Hoodie&eyeType=Default&mouthType=Default`;
2
3

And, we should create a state to manage the image src:

1  const [avatar, setAvatar] = useState(defaultAvatar);
2

Then, in the <img /> tag:

1<img 
2  className={styles.image}
3  alt='avatar'
4  src={avatar}
5/>
6

Now, the card should look like this:

To finish, let's add some logic in the updateAvatarHandler method:

1const updateAvatarHandler = (queries) => {
2  const newQueryArr = Object.entries(queries).map((query) => {
3    return `${query[0]}=${query[1]}`;
4  });
5  setAvatar(`https://avataaars.io/?avatarStyle=Transparent&clotheColor=Blue03&${newQueryArr.join('&')}`);
6}
7

In this method, we are receiving an object with the keys and values needed to set the avatar through queryParams: So we just need to use Object.entries to get an array and the .map() method to evaluate the values and create a new array for each queryParam.

To finish, we call setAvatar method to update the src element in the image, and, using the .join method in the new array, we convert the array to a string separated by the & symbol in order to separate the different queryParams.

That's all. The final app should work like this:

Summary

In this project, we learned how to create a basic application to customize an avatar using React and avatar-generator

You can find the code here