How to structure a reusable button component for your next React application. Use these techniques for your generic components to create highly reusable components.
MouseEvent<HTMLButtonElement>
, what if I wanted this button to be an anchor (<a>
) instead? The HTMLButtonElement
would not apply. Additionally, onClick
might not apply at all, we might just want the styling and pass in a href
.
We can do some interface tricks for this to work.
<Link>
component, however it is used to wrap an <a>
tag. If we want to use the passHref
prop, we’ll probably have to tweak the interface, since now, href
can be optional and it might have to work with forwardRef
, which needs to work with both <a>
and <button>
.
I think at this point we can agree this is probably not useful, and will cause a simple button, that is used throughout your project, to be insanely complex.
Button
components, allows you to switch the underlying DOM node. Many times they use the as
keyword because of styled components. So should we just use that library or styled components?
When I have seen this used, it has been in the context of keeping styling consistent and easily editable at a global level. Many times we have to alter the className
of a button based on the props. So if one of the main reasons is styling, why don’t we just abstract that out and have different components for a <a>
and <button>
styled buttons? Once we use a button in our application and decided on the element needed, what are the chances that it will need to change ever? Probably really low, like really low. So lets address the styling shall we.
BaseButton
interface is staying. WOOOO. But not in the same way.
className
of the button? Easy, write a hook.
React Hooks don’t have to wrap other hooks (useState
for example), they can just be used to wrap functionality. We will also make use of a npm package called classnames
, this allows us to easily create className
strings. It isn’t required, just makes life easier.
extend
other interfaces which helps to create more generic interfaces and build up the complex interfaces. One really useful aspect here is the ability to extends HTML attributes based on the element.
<button>
element without needing to define them yourselves. Depending on your project, you might not want/need this flexibilty. However, for the examples below, I will be using them.
Anchor
is a <a>
and a Button
is a <button>
. Additionally, it will make other components in your application a lot more descriptive without needing inspect the props to determine what the component will render.
Additionally, just in these two components there were a very small changes that would have been, not annoying, but would add additional conditional into your base component to achieve which HTML element is rendered, what additional classes should be added etc.
You’ll also notice that our interfaces are really simple, the HTMLAttributes
interface really does the heavy lifting for us in this case, again, making these components feel more native, which is exactly what you want for a Block/Atom Component.
Anchor
in a forwardRef
. This will allow us to use this custom component with the Next.js Link
component.
Button
at all, and existing Anchor
elements in the app will work perfectly fine as they did before. We were also able to type the ref object to be specifically a HTMLAnchorElement
instead of a HTMLAnchorElement|HTMLButtonElement
. Which again, adds in those conditional checks and also might behave weirdly with the <Link>
component.