📜 ⬆️ ⬇️

Ten Commandments React Components

the-10-component-commandments


Written by Kristofer Selbekk , in collaboration with Caroline Odden . Based on a lecture with the same name and with the same people, held at the ReactJS meeting in Oslo in June 2019.

From the translator - the original title of The 10 Component Commandments does not mention React, but most of the examples and recommendations refer specifically to the reactor, besides the article is posted under the react tag and written by the developers .


It is not easy to create components that will be used by many developers. You have to think very carefully about which props to use if these props are part of a public API.


In this article we will give a brief introduction to some of the best practices in the development of the API as a whole, as well as form ten useful commandments that you can use to create components that your fellow developers will be happy to use.


api


What is an API?


API - or application programming interface - a place where there are two pieces of code. This is the contact surface between your code and the rest of the world. We call this surface interface. This is a specific set of actions or data points that you can interact with.


The interface between the class and the code that calls this class is the same API. You can call class methods to get data or run functions contained in it.


Following the same principle, the props that your component accepts are its API . This is the way developers interact with your component.


Some of the best API design practices


So, what rules and considerations apply when developing an API? Well, we did a little research and it turned out that there are many excellent resources on this topic. We chose two - Josh Tauberer - "What makes a good API?" and the article of Ron Kurir with the same title - and came to these four practices.


Stable versions


One of the most important things to consider when creating an API is to keep it as stable as possible. The number of critical changes should be minimal. If you have to make critical changes, be sure to write detailed upgrade guides and, if possible, write a code mod that automates this process for developers.


Descriptive Error Messages


Whenever an error occurs when calling your API, you need to do everything possible to explain what went wrong and how to fix it. If you scold the user with messages like "misuse" and do not give any explanation, your API will leave a bad impression.


Instead, write descriptive errors that will help the user to correct how they use your API.


Minimize developer surprises


Developers are fragile creatures, and you shouldn't scare them when they use your API. In other words - make your API as intuitive as possible. You will achieve this if you follow best practices and existing naming conventions.


Also, your code should always be consistent. If you use logical property names with is or has in one place, but skip them further, it will confuse people.


Minimize API surface


Your API should also be minimized. A lot of opportunities are great, but the smaller the surface of your API (API surface), the less developers will have to learn it in order to start working productively with it. Thanks to this, your API will be perceived as easy to use!


There is always a way to control the size of your API. One of them is the refactoring of the new API from the old one.


Ten Commandments for Web Components


The 10 Component Commandments


So, these four golden rules work well for the REST API and for the old procedural procedural pieces on Pascal - but how to transfer them to the modern world of React?


As we said earlier, components have their own API. We call them props , and it is through them that the data is transferred to the components. How do we structure the props so as not to violate any of the above rules?


We have created this list of ten golden rules that are best followed when creating your components. We hope that they will be useful to you.


1. Document the use of components.


If the way your component is to be used is not documented, then this component is useless. Well, almost useless, you can always look at its implementation, but few people like to do it.


There are many ways to document your components, but we recommend paying attention to these three:



The first two give you a platform to work on when developing your components, and the third allows you to write free-form documentation using MDX


No matter what you choose, always document both the API itself and how your components should be used . The last part is crucial in general-purpose libraries — so that people use the button or layout grid correctly in a given context.


2. Allow contextual semantics


HTML is a language for structuring information in a semantic way. Only here the majority of our components consists of <div /> tags. It makes sense - universal components cannot know in advance what they will be, <article /> , or <section /> , or <aside /> , but this situation is far from ideal.


There is another option, just let your components take prop as , and thereby determine which element of the DOM will be rendered. Here is an example of how this can be implemented:


 function Grid({ as: Element, ...props }) { return <Element className="grid" {...props} /> } Grid.defaultProps = { as: 'div', }; 

We rename the as as variable to the Element variable and use it in our JSX. We give the default total div if we don't have a more semantic HTML tag to pass.


When it comes time to use the <Grid /> component, you can simply pass the correct tag:


 function App() { return ( <Grid as="main"> <MoreContent /> </Grid> ); } 

This also works with React components. For example, if you want the <Button /> component to render the React Router <Link /> :


 <Button as={Link} to="/profile"> Go to Profile </Button> 

3. Avoid logical (boolean) props.


Logical predictions are a good idea. They can be used without value, so it looks very elegant:


 <Button large>BUY NOW!</Button> 

But even though it looks pretty, logical properties allow only two possibilities. On or off Visible or hidden. 1 or 0.


Whenever you start to enter logical properties for things like size, options, colors, or something that can be anything other than a binary choice, you have problems.


 <Button large small primary disabled secondary>   ?? </Button> 

In other words, logical properties often do not scale with changing requirements. Instead, for values ​​that may become something other than a binary choice, it’s better to use enumerated values, such as strings.


 <Button variant="primary" size="large">     </Button> 

This does not mean that logical properties cannot be used at all. Can! The prop disabled , which I listed above, still has to be logical - because there is no average state between on and off. Just leave the logical properties only for a truly binary choice.


4. Use props.children


React has several special properties that are not considered as others. One such key needed to keep track of the order of the list items. And one more such special prop is children .


Everything that you put between the opening and closing tags of a component is placed inside the props.children . And you should use this as often as possible.


Why? Because it's much easier than having content prop for content or something like that, which usually takes only simple values, such as text.


 <TableCell content="Some text" /> //  <TableCell>Some text</TableCell> 

There are several advantages to using props.children . First of all, this is similar to how plain HTML works. Secondly, you can freely transfer whatever you want! Instead of adding props like leftIcon and rightIcon to your component, just pass them as part of the props.children :


 <TableCell> <ImportantIcon /> Some text </TableCell> 

You can argue that your component only needs to render plain text, and in some cases it is. Up to a point. Using the same props.children , you guarantee that your API will be ready for changing requirements.


5. Allow parent to cling to internal logic.


Sometimes we create components with a lot of internal logic and states - for example, autocomplete or interactive diagrams.


Such components often suffer from excessive APIs, one of the reasons for this is the large number of different use cases that accumulate with the development of the project.


But what if we could just provide a single, standardized prop that would allow a developer to control, respond to, or just change the default behavior of a component?


Kent Dodds wrote an excellent article on the concept of state reducers. Here is an article about the concept itself , and also, here is an article about how to implement this for React hooks .


In short, this is the state redundancy feature transfer pattern to your component, which will allow the developer to access all the actions performed inside your component. You can change the state or even cause side effects. This is a great way to provide a high level of customization, without any props .


Here is what it might look like:


 function MyCustomDropdown(props) { const stateReducer = (state, action) => { if (action.type === Dropdown.actions.CLOSE) { buttonRef.current.focus(); } }; return ( <> <Dropdown stateReducer={stateReducer} {...props} /> <Button ref={buttonRef}>Open</Button> </> } 

By the way, you can create easier ways to respond to events. Using the onClose in the previous example is likely to make using the component more convenient. Use the "state reducer" pattern when you need it.


6. Use the operator ellipsis (spread) for the remaining props


Every time you create a new component - make sure that you apply a three-dot to the remaining props and send them to the element for which this makes sense.


You do not need to continue adding props to your component, which will simply be transferred to the base component or element. This will make your API more stable, eliminating the need for many minor version errors when the next developer needs a new event listener or ARIA tag.


You can do it like this:


 function ToolTip({ isVisible, ...rest }) { return isVisible ? <span role="tooltip" {...rest} /> : null; } 

Whenever your component passes a prop to your implementation, such as the class name or onClick handler, make sure that another developer can do the same. In the case of a class, you can simply add a prop class using the convenient npm classnames library (or simply by concatenating strings):


 import classNames from 'classnames'; function ToolTip(props) { return ( <span {...props} className={classNames('tooltip', props.tooltip)} /> } 

In the case of click handlers and with other callbacks, you can combine them into one function with a small utility. Here is one way to do this:


 function combine(...functions) { return (...args) => functions .filter(func => typeof func === 'function') .forEach(func => func(...args)); } 

Here we create a function that takes a list of functions to combine them. It returns a new callback that calls them all in turn with the same arguments.


This function can be used in this way:


 function ToolTip(props) { const [isVisible, setVisible] = React.useState(false); return ( <span {...props} className={classNames('tooltip', props.className)} onMouseIn={combine(() => setVisible(true), props.onMouseIn)} onMouseOut={combine(() => setVisible(false), props.onMouseOut)} /> ); } 

7. Use default values.


Make sure you give enough defaults for your props. Thus, you will reduce the number of required props. This will greatly simplify your API.


Take, for example, the onClick handler. If your code does not need this handler, use the empty function (noop-function) as the default prop. This way you can call it in your code as if it were always transmitted.


Another example might be for user input. Suppose that the input string is an empty string, unless otherwise specified. This will allow you to make sure that you are always dealing with a string object, and not with something vague or null.


8. No need to rename HTML attributes


HTML as a language has its own props - or attributes, and it itself is an API interface for HTML elements. So why not continue to use this API?


As we mentioned earlier, minimizing the API surface (API surface) and its intuitiveness are useful methods for improving the API of your components. So instead of creating your own screenReaderLabel prop, why not just use an existing aria-label ?


Stay away from renaming any existing HTML attributes for your own "ease of use." You do not even replace the existing API - you just add your own on top of it. People can still pass aria-label along with your screenReaderLabel screenReaderLabel - and then what should be the final value?


Also, make sure that you never override the HTML attributes in your components. A great example is the type attribute of the <button /> element. This can be submit (default), button or reset . However, many developers override this prop to indicate the visual type of the button ( primary , cta , etc.).


If you use such a prop, you must add an override for this type attribute. This will lead to confusion, doubt and irritation on the part of the developers.


Believe me - I made this mistake again and again - if you make it, then you have to clear it up for a long time.


9. Write types of props (or just types)


No documentation will be as good as the documentation that lives inside your code. React comes with a great way to declare your API components using the prop-types package. Use it.


You can specify any requirements for the format of your mandatory and optional props, besides, you can improve them with the help of JSDoc comments .


If you do not specify a required proc or pass an invalid or unexpected value, you will receive a warning in the console at runtime. This is very helpful during development, and can be removed from production.


If you write your React applications in TypeScript or with Flow, you get API documentation in the form of a language function. This further increases support from the development tools and simplifies the work.


If you do not use typed javascript yourself, you should still think about providing type definitions to developers who use it. Then it will be much easier for them to use your components.


10. Design for developers


Finally, the most important rule to follow. Make sure that your API and work with your components are optimized for developers who will use it.


One way to simplify a developer’s work is to give him inappropriate feedback. Do this with error messages (errors), and also, but only during the development itself, with warnings (warnings) that there are better ways to use your component.


When writing errors and warnings, give links to your documentation or show simple code examples. The faster the developer understands what the problem is and how to fix it, the more comfortable your component will be to work with.


Incredibly, as it turned out, the presence of all these long error warnings does not affect the size of the final package. Thanks to the wonders of eliminating dead code, all this text and error code can be removed during assembly into production.


One of the libraries that gives feedback is incredibly good is React itself. It doesn't matter if you forgot to specify a key for the list items or incorrectly wrote the life cycle method, or maybe you forgot to extend the base class or called the hook in an indefinite way - in any case, you will get big fat error messages in the console. Why should developers who use your components expect anything less from you?


So design for your future users. Design for yourself from the future. Design for the unfortunate who will have to maintain your code when you are gone! Design for developers.


Total


We can learn a lot from the classic API approach. Following the tips, tricks, rules and commandments of this article, you can create components that are easy to use, easy to maintain, intuitive and, if necessary, very flexible.


')

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


All Articles