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
We have a switch component - Switch
, which accepts a prop, let's call it something
(something) for now.
A developer using our component can transfer a function, and we will call it when the value changes.
<Switch something={fn} />
React gives us the opportunity to call the prop as we please: handler
/ clickHandler
/ onClick
/ onToggle
, etc.
The agreement that the name of the event handler should start with on
, for example, onClick
, has onClick
. This is due to the fact that in the HTML specification there are many handlers that already follow this agreement: onkeydown
, onchange
, onclick
, etc.
Reusing an existing agreement is a great idea; developers will not have to memorize anything new.
Okay, how about onClick
?
<Switch onClick={fn} />
Here I am not a supporter of the name onClick
, this name suggests that clicking the mouse is the only way to interact with this component.
Users on the mobile device can press the switch with a finger or drag it to the right. Visually impaired users can use it with screen reader software and use keyboard keys.
As a developer using this component, I don’t want to think about how end users interact with this component. I just want to attach a function that is called when the value changes.
Let us give names for your API that will not indicate the way of interaction:
<Switch onToggle={fn} />
That makes sense, right? Switch toggles
(toggled) between two values.
Inside the component, you may want to proxy all possible interactions in the same function.
function Switch(props) { return ( <div className="switch" /* */ onClick={props.onToggle} onKeyDown={function(event) { /* enter , event */ if (event.key === 'Enter') props.onToggle(event) }} onDrag={function(event) { /* */ if (event.toElement === rightSide) props.onToggle(event) }} /> ) }
We took into account all the options to give a clear API to our users (developers).
Now let's talk about the text entry component.
<TextInput />
HTML has an onchange attribute , the React documentation uses onChange
in its examples. Apparently, there is a consensus on this.
<TextInput onChange={fn} />
Very simple.
Now let's put both of these components together.
<TextInput onChange={fn} /> <Switch onToggle={fn} />
See something strange?
Although both components require the same behavior, a prop is called differently. These props are ideal for their respective components, but when you look at your components together, it looks rather contradictory.
The developer who will use these components will always have to check the name of the prop before using it.
So here's tip number 2: aim for consistent props between components . The same behavior should have the same prop for all components.
This advice can also be summarized as follows: Aim for the minimum API area . You must limit the number of APIs that a developer must master before he starts to work productively.
I want to say that all the merits on this topic go to Sebastian MarkbĂĄge . (His performance: Minimal API Surface Area )
The way to implement this advice is to select one prop and use it in all of your components. Of the two props that we have in our example, onChange
also in the HTML specification, so some developers might recognize it.
<TextInput onChange={fn} /> <Switch onChange={fn} /> <Select onChange={fn} /> // etc.
The consistency between the components and, as a result, the ease of exploring your API, outweighs the choice of the “perfect” prop for a single component.
A small bonus.
Let's talk about the signature of this function.
<TextInput onChange={fn} />
The onChange
event onChange
( fn
in this example) takes one argument, event
.
It works with every change. You can get a lot of useful information from this event.
function fn(event) { console.log(event.target) // console.log(event.target.value) // console.log(event.which) // }
Most likely, most developers will be interested in event.target.value
so that they can use it for some other tasks - using in state, submitting a form, etc.
In the case of our Switch
component, each action represents a separate "event" - event
. This event
will have different properties for clicking and dragging. How do we make sure the API is consistent?
We can manually set event.target.value
for each "event":
function Switch(props) { /* */ const fireHandler = event => { const newValue = !oldValue /* , : */ event.target.value = newValue /* */ props.onChange(event) } return ( <div className="switch" /* */ onClick={fireHandler} onKeyDown={function(event) { if (event.key === 'Enter') fireHandler(event) }} onDrag={function(event) { if (event.toElement === rightSide) fireHandler(event) }} /> ) }
Source: https://habr.com/ru/post/459378/
All Articles