📜 ⬆️ ⬇️

Writing API for React components, part 4: beware of Apropacalypse

Writing API for React components, part 1: do not create conflicting props

We write API for React components, part 2: let's name the behavior, not the way of interaction

We write API for React components, part 3: the order of props is important
')
Writing an API for React components, part 4: beware of Apropacalypse!

Writing an API for React components, part 5: just use composition

We write API for React components, part 6: we create communication between components

Let's talk about the Avatar component.


avatar-1


 <Avatar image="simons-cat.png" /> 

Avatars are found everywhere in applications and usually come in different sizes. You need a large avatar for a user profile, a small drop-down list and a few intermediate ones.


github-avatars


Let's add a prop size ( size prop).


We do not want to give the developer the opportunity to set an arbitrary width and height, instead we want to give the developer several possible sizes.


avatar-2


 <Avatar size="xsmall" image="simons-cat.png" /> <Avatar size="small" image="simons-cat.png" /> <Avatar size="medium" image="simons-cat.png" /> <Avatar size="large" image="simons-cat.png" /> <Avatar size="xlarge" image="simons-cat.png" /> 

In cosmos , we still have avatars for applications. We want them to look a little different - a rounded square instead of a circle.


Part of creating a good API is to give developers the opportunity to think about data, not about design - the design should already be in the component

We can add another prop that distinguishes between two types of avatar. One little prop can't hurt, right?


avatar-3


 <Avatar type="user" image="simons-cat.png" /> <Avatar type="app" image="firebase.png" /> 

Looks good, isn't it?


And yes, we get support for several sizes for the avatar of the application, because it is the same component. In general, we do not need it, but why not, since it cost us nothing


avatar-4


 <Avatar type="app" size="xsmall" image="firebase.png" /> <Avatar type="app" size="small" image="firebase.png" /> <Avatar type="app" size="medium" image="firebase.png" /> <Avatar type="app" size="large" image="firebase.png" /> <Avatar type="app" size="xlarge" image="firebase.png" /> 

Let's talk about how a developer will actually use this component in his application. User information probably comes from the API and contains the URL of the avatar. The developer will pass this information to the Avatar component using props.


And if the user has not yet loaded the avatar, we want to show the default value, the same goes for the application logo.


avatar-6


 <Avatar type="user" image={props.user.avatar} /> <Avatar type="user" image={missing} /> <Avatar type="app" image={props.app.logo} /> <Avatar type="app" image={missing} /> 

The default image is already included in the component, so we don’t ask the image developer for the default image.


This is a good backup option, but we can do more.


We can show the initials of the username with a unique background (Gmail made this template popular, it helps to quickly distinguish people)


avatar-7


 <Avatar type="user" image={props.user.avatar} /> <Avatar type="user" image={missing} /> <Avatar type="user" initials={props.user.intials} image={missing} /> <Avatar type="app" image={props.app.logo} /> <Avatar type="app" image={missing} /> <Avatar type="app" icon={props.app.type} image={missing} /> 

Let's look at all the props that our component supports:


namedescriptiontype ofdefault value
imageImage URLstring-
sizeavatar sizestring: [xsmall, small, medium, large, xlarge]small
typeavatar typestring: [user, app]user
initialsuser initials as a fallbackstring-
iconicon to display as a fallbackstring: [list of icons]-

We started with a simple avatar component, but now it supports all these props and behavior!


When you see a component that supports a lot of props, it probably tries to do too many things. You have just created the Apropcalypse .


Invented this concept Jenn Creighton .


We are trying to make our Avatar component work for users and applications, and at the same time work with different sizes and different options for backup behavior.


It also allows strange combinations that are not recommended for use, such as avatar applications with backup text. Remember, this was tip # 1 - do not create conflicting props (see Writing API for React components, part 1: do not create conflicting props )!


Okay, how are we gonna deal with this? Create two different components.


Tip:


Do not be afraid to create a new component instead of adding props and additional logic to an already existing component.

First, here’s what the API for two different avatar components will look like:


avatar-7


 <UserAvatar size="large" image="simons-cat.png" /> <UserAvatar size="large" image="" /> <UserAvatar size="large" fallback="LA" image="" /> <AppAvatar image="firebase.png" /> <AppAvatar image="" /> <AppAvatar fallback="database" image="" /> 

There are several things in this API that you should pay attention to:


  1. We were able to remove unnecessary features, such as size , in AppAvatar .
  2. We reduced the size of our API by keeping the same name fallback (fallback) for the two components.
    Remember tip number 2 of this series ? We want developers to think about the behavior ( fallback ), and not about the interaction / implementation (initials or icons) . This helps developers learn more about APIs and how to use components.
  3. We can also avoid any conflicting props .
  4. Finally, we reduced the number of props. Look at the props table, it has become much cleaner:

UserAvatar:


namedescriptiontype ofdefault value
imageImage URLstring-
sizeavatar sizestring: [xsmall, small, medium, large, xlarge]small
fallbackuser initials as a fallbackstring-

AppAvatar:


namedescriptiontype ofdefault value
imageImage URLstring-
fallbackicon as a fallbackstring: [list of icons]-

The only thing that saddens me a little in this API is that although both components have the same name and type for fallback: string (a fallback with the type string is a string), one of them accepts two letters for initials, while while another is the name of the icon.




Let's talk about the implementation. It is tempting to create the BaseAvatar component, which both UserAvatar and UserAvatar will use, while some props will be blocked.


 function UserAvatar(props) { return ( <BaseAvatar image={props.image} type="user" initials={props.fallback} /> ) } render(<UserAvatar fallback="LA" />) 

And this is not a bad idea at all. But it is rather difficult at the very beginning to foresee what we may need. We do not predict future requirements.


Start with two different components, and as they evolve, you can begin to see similar patterns and find a good abstraction. The absence of abstraction is much better than the wrong abstraction.


Duplication is better than incorrect abstraction - Sandi Metz

Go back to your code and find the component that takes too many props, and see if you can simplify it by breaking it into several components.




For further study:


  1. lecture by Jenn Creighton , where she talks about Apropacalips
  2. Sandi Metz lecture on duplicates and abstractions

Source: https://habr.com/ru/post/459414/


All Articles