Useful React APIs For Building Flexible Components With TypeScript
Have you ever used React.createElement
directly? What about React.cloneElement
? React is more than just transforming your JSX into HTML. Much more, and to help you level up your knowledge of lesser-known (but very useful) APIs the React library ships with. We’re going to go over a few of them and some of their use cases that can drastically enhance your components’ integration and usefulness.
In this article, we’ll go over a few useful React APIs that are not as commonly known but extremely useful for web developers. Readers should be experienced with React and JSX syntax, Typescript knowledge is helpful but not necessary. Readers will walk away with everything they need to know in order to greatly enhance React components when using them in React applications.
React.cloneElement
Most developers may never have heard of cloneElement
or ever used it. It was relatively recently introduced to replace the now deprecated cloneWithProps
function. cloneElement
clones an element, it also lets you merge new props with the existing element, modifying them or overriding them as you see fit. This opens up extremely powerful options for building world-class APIs for functional components. Take a look at the signature.
function cloneElement( element, props?, ...children)
Here’s the condensed Typescript version:
function cloneElement(
element: ReactElement,
props?: HTMLAttributes,
...children: ReactNode[]): ReactElement
You can take an element, modify it, even override its children, and then return it as a new element. Take a look at the following example. Let’s say we want to create a TabBar component of links. That might look something like this.
export interface ITabbarProps {
links: {title: string, url: string}[]
}
export default function Tabbar(props: ITabbarProps) {
return (
<>
{props.links.map((e, i) =>
<a key={i} href={e.url}>{e.title}</a>
)}
</>
)
}
The TabBar is a list of links, but we need a way to define two pieces of data, the title of the link, and the URL. So we’ll want a data structure passed in with this information. So our developer would make our component like so.
function App() {
return (
<Tabbar links={[
{title: 'First', url: '/first'},
{title: 'Second', url: '/second'}]
} />
)
}
This is great, but what if the user wants to render button
elements instead of a
elements? Well, we could add another property that tells the component what type of element to render.
But you can see how this will quickly get unwieldy, we would need to support more and more properties to handle various use cases and edge cases for maximum flexibility.
Here’s a better way, using React.cloneElement
.
We’ll start by changing our interface to reference the ReactNode
type. This is a generic type that encompasses anything React can render, typically JSX Elements but also can be strings and even null
. This is useful for designating you to want to accept React components or JSX as arguments inline.
export interface ITabbarProps {
links: ReactNode[]
}
Now we’re asking the user to give us some React Elements, and we’ll render them how we want.
function Tabbar(props: ITabbarProps) {
return (
<>
{props.links.map((e, i) =>
e // simply return the element itself
)}
</>
)
}
This is perfectly valid and would render our elements. But we’re forgetting a couple of things. For one, key
! We want to add keys so React can render our lists efficiently. We also want to alter our elements to make necessary transformations so they fit into our styling, such as className
, and so on.
We can do these with React.cloneElement
, and another function React.isValidElement
for checking the argument conforms to what we’re expecting!
React.isValidElement
This function returns true
if an element is a valid React Element and React can render it. Here’s an example of modifying the elements from the previous example.
function Tabbar(props: ITabbarProps) {
return (
<>
{props.links.map((e, i) =>
isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'})
)}
</>
)
}
Here we’re adding a key prop to each element we’re passing in and making every link bold at the same time! We can now accept arbitrary React Elements as props like so:
function App() {
return (
<Tabbar links={[
<a href='/first'>First</a>,
<button type='button'>Second</button>
]} />
)
}
“
The advantage here is if we wanted to set a custom onClick
handler to our button, we could do so. Accepting React elements themselves as arguments is a powerful way to give flexibility to your component design.
useState
Setter Function
Use Hooks! The useState
hook is extremely useful and a fantastic API for quickly building state into your components like so:
const [myValue, setMyValue] = useState()
Due to the JavaScript runtime, it can have some hiccups. Remember closures?
In certain situations, a variable might not be the correct value because of the context it is in, such as in for-loops commonly or asynchronous events. This is because of lexical scoping. When a new function is created the lexical scope is preserved. Because there is no new function, the lexical scope of newVal
is not preserved, and so the value is actually dereferenced by the time it is used.
setTimeout(() => {
setMyValue(newVal) // this will not work
}, 1000)
What you’ll need to do is utilize the setter as a function. By creating a new function the variables reference is preserved in lexical scope and the currentVal is passed in by the React useState Hook itself.
setTimeout(() => {
setMyValue((currentVal) => {
return newVal
})
}, 1000)
This will ensure that your value is updated correctly because the setter function is called in the correct context. What React does here is call your function in the correct context for a React state update to occur. This can also be used in other situations where it’s helpful to act on the current value, React calls your function with the first argument as the current value.
Note: For additional reading on the topic of async and closures, I recommend reading “useState
Lazy Initialization And Function Updates” by Kent C. Dodds.
JSX Inline Functions
Here’s a Codepen demo of a JSX inline function:
Not exactly a React API per-say.
“
Here’s an example:
function App() {
return (
<>
{(() => {
const darkMode = isDarkMode()
if (darkMode) {
return (
<div className='dark-mode'></div>
)
} else {
return (
<div className='light-mode'></div>
) // we can declare JSX anywhere!
}
})()} // don't forget to call the function!
</>
)
}
Here we’re declaring code inside of JSX, we can run arbitrary code and all we have to do is return a JSX function to be rendered.
We can make it conditional, or simply perform some logic. Take note of the parentheses surrounding the inline function. Also particularly here where we are calling this function, we could even pass an argument into this function from the surrounding context if we wanted to!
})()}
This can be useful in situations where you want to act on a collection data structure in a more complex way than a standard .map
allows for inside of a JSX element.
function App() {
return (
<>
{(() => {
let str = ''
for (let i = 0; i < 10; i++) {
str += i
}
return (<p>{str}</p>)
})()}
</>
)
}
Here we can run some code to loop through a set of numbers and then display them inline. If you use a static site generator such as Gatsby, this step would be pre-computed as well.
component extends type
Immensely useful for creating autocomplete-friendly components, this feature allows you to create components that extend existing HTMLElements
or other components. Mostly useful for correctly typing an elements interface in Typescript but the actual application is the same for JavaScript.
Here’s a simple example, let’s say we want to override one or two properties of a button
element, but still give developers the option to add other properties to the button. Such as setting type='button'
or type='submit'
. We obviously don’t want to recreate the entire button element, we just want to extend its existing properties, and maybe add one more prop.
import React, { ButtonHTMLAttributes } from 'react'
First we import React and the ButtonHTMLAttributes
class, a type that encompasses the props of a HTMLButtonElement
. You can read the source code for this type of interface here:
And you can see the React team has reimplemented all of the web’s APIs in TypeScript so can be type-checked.
Next, we declare our interface like so, adding our status
property.
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
status?: 'primary' | 'info' | 'danger'
}
And finally, we do a couple of things. We use ES6 destructuring to pull out the props that we care about (status
, and children
), and declare any other properties as rest
. And in our JSX output, we return a button element, with ES6 structuring to add any additional properties to this element.
function Button(props: ButtonProps) {
const { status, children, ...rest } = props // rest has any other props
return (
<button
className={`${status}`}
{...rest} // we pass the rest of the props back into the element
>
{children}
</button>
)
}
So now a developer can add a type
prop or any other prop that a button would typically have. We’ve given an additional prop that we’ve utilized in the className
to set the style of the button.
Here’s the entire example:
import React, { ButtonHTMLAttributes } from 'react'
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
status?: 'primary' | 'info' | 'danger'
}
export default function Button(props: ButtonProps) {
const { status, children, ...rest } = props
return (
<button
className={`${status}`}
{...rest}
>
{children}
</button>
)
}
This makes for a great way of creating reusable internal components that conform to your style guidelines without rebuilding entire HTML elements! You can simply override entire props such as setting the className
based on the status or allow for additional class names to be passed in as well.
import React, { ButtonHTMLAttributes } from 'react'
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
status?: 'primary' | 'info' | 'danger'
}
export default function Button(props: ButtonProps) {
const { status, children, className, ...rest } = props
return (
<button
className={`${status || ''} ${className || ''}`}
{...rest}
>
{children}
</button>
)
}
Here we take the prop className
passed to our Button element, and insert it back in, with a safety check in the case of the prop being undefined
.
Conclusion
React is an extremely powerful library, and there’s a good reason why it has quickly gained popularity. It gives you a great tool-set to build performant and easy-to-maintain web apps. It’s extremely flexible and yet very strict at the same time, which can be incredibly useful if you know how to use it. These are just a few APIs that are noteworthy and are largely overlooked. Give them a try in your next project!
For further reading about the latest React APIs, hooks, I would recommend reading useHooks(🐠). The Typescript Cheatsheet also has some great information for React and Typescript Hooks.