Introduction
Typically, React components are built out of plain old HTML elements like <div>
and <span>
, but by creating our own foundational component, which we've named VisageComponent
, we're able to easily share functionality across all of our components without duplicating code.
Here's how we might build a Button
component using the standard HTML <button>
tag:
function Button(props) {return <button {...props} />}
This Button
forwards all props directly to the underlying <button>
, so our Button
implicitly understands all of <button>
's props (e.g. onClick
, className
, etc.)
<Button onClick={() => alert('clicked!')}>Click me!</Button>
We can take that a step further though. Let's take a look at how we might build a Button
component using VisageComponent
:
function Button(props) {return <VisageComponent component="button" {...props} />}
Just like before, any props passed to Button
will be forwarded to the underlying component. But rather than using a <button>
as the underlying component, we use our VisageComponent
. So, this Button
understands all props that VisageComponent
understands.
And if we take a look at a simple implementation of VisageComponent
:
function VisageComponent(props) {const { component, ...otherProps } = props// we need to rename to an uppercase variable or else JSX gets madconst Comp = componentreturn <Comp {...otherProps} />}
Our VisageComponent
renders whatever is passed via the component
prop and forwards all of its props to that component.
So, if you think about it, we're actually in the same position that we were in before.
Our first Button
just passed all props directly to a <button>
and, by doing so, gained an implicit understanding of all of <button>
s props. And our second Button
passes all of its props to VisageComponent
which passes all of its props to a <button>
, so we still get that implicit understanding of all of <button>
's props, but--crucially--VisageComponent
gets to look at the props before they get to the <button>
.
This allows us to intercept those props and to do whatever we want!
Shared Functionality
This enables us, for example, to effectively add a prop to every one of our components by simply adding that prop to VisageComponent
.
Let's say that we want all of our components to have a prop called shouldRenderAnEmoji
. If we pass shouldRenderAnEmoji
to any component, that component should render an emoji rather than whatever else it was going to render. Very useful.
Now, we could go and add that logic to all 60+ of our components, or we could just add it VisageComponent
!
Let's update our VisageComponent
to add the functionality:
function VisageComponent(props) {const { component, shouldRenderAnEmoji, ...otherProps } = propsconst Comp = componentif (shouldRenderAnEmoji) {return <Comp>😅</Comp>}return <Comp {...otherProps} />}
Now, any component that was built with VisageComponent
will implicitly understand the shouldRenderAnEmoji
prop!
<Button shouldRenderEmoji /> // <button>😅</button>
The rest of this article will cover all of the shared functionality that we've currently built in to VisageComponent
.
Component Prop
As we've seen, VisageComponent
accepts a prop called component
that allows you to specify exactly which component should be rendered. Each of our components (e.g. TextButton
, Collapsible
, etc.) provide a default value for component
, but if you provide you pass a value for the component
prop yourself, your value will override the default component!
// TextButton uses component="button" by default// but you can easily pass your own value for `component`!// note how we pass an `href` now that we know that an `<a>` is going to be rendered!;<TextButton component="a" href="/">All the styles from TextButton, but is actually using an anchor tag!</TextButton>
The component
prop provides a way to override the default component that is rendered when you use any of the React Visage components. The component
prop accepts any HTML tag name (e.g. component="div"
) or any React component (e.g. component={ReactRouterLink}
).
Semantics
In the world of HTML, semantics are of utmost importance.
Creating semantically-correct markup has a few key benefits:
Accessibility - screen-readers and other assistive technologies are able to better understand semantic pages which results in a much better experience for folks who use those tools
SEO - site-traversing bots (like Google's "spiders") are able to better understand semantic pages which can result in better search engine ranking
Developer Experience (DX) - Semantic HTML is easier to read/maintain/navigate which can actually speed up development
By default, the React Visage will render the most semantically-correct HTML that it can. e.g. TextButton
will render a button
and Link
will render an a
tag, but there are often cases where the proper HTML is based on context to which React Visage does not have access.
That is why we provide the component
prop!
Here, we'll provide a few examples of where you might apply the component prop.
Grid Example
GridContainer
, Row
, and Column
typically don't carry any semantic meaning; they're just used for layout, so they render div
s by default.
However, if we were rendering a list of items in a shopping cart where each item was in its own Row
, then our GridContainer
is semantically a list and each Row
is a list-item.
Using the component
prop, we can easily encode that information
<GridContainer component="ul"><Row component="li"><Column span={12}>Cart item 1</Column></Row><Row component="li"><Column span={12}>Cart item 2</Column></Row></GridContainer>
Just like that, we've improved the screen-reader experience dramatically by allowing screen-reading tools to announce this section of the page as "a list containing 2 items" rather than "err I don't know what this part of the page is; it's just a bunch of div
s" (disclaimer: these are not actually screen-reader quotes)
Other props
The React Visage base component also adds these props to all React components: