Useful React APIs For Building Flexible Components With TypeScript — Smashing Magazine

Quick summary ↬

React with JSX is a great tool for making user-friendly components. Typescript components make it an absolute pleasure for developers to integrate your components into their apps and explore your APIs. Learn about three lesser-known React APIs that can take your components to the next level and help you build even better React Components in this article.

Have you ever used React.createElement direct? What about React.cloneElement? React is more than just transforming your JSX into HTML. Much more, and to help you increase your knowledge of lesser known (but very useful) APIs that the React library comes with. We will review a few of them and some of their application cases that can drastically improve the integration and usability of your components.

In this article, we will go over a few useful React APIs that are not so commonly known but extremely useful for web developers. Readers should have experience with React and JSX syntax. Typescript knowledge is useful but not necessary. Readers will walk away with everything they need to know to greatly enhance React components when using them in React applications.

React.cloneElement

Most developers may have never heard of it cloneElement or ever used it. It was introduced relatively recently to replace the now phased out ones cloneWithProps function. cloneElement clones an item, it also lets you merge new props with the existing item, modify them, or override them as you see fit. This opens up extremely powerful capabilities for building world-class functional APIs. Take a look at the signature.

function cloneElement( element, props?, ...children)

Here is the condensed Typescript version:

function cloneElement( 
   element: ReactElement, 
   props?: HTMLAttributes, 
   ...children: ReactNode[]): ReactElement

You can take an item, change it, even override its children, and then return it as a new item. Take a look at the following example. Let’s say we want to create one TabBar part of links. It can look 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>
     )}
   </>
 )
}

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 want a data structure passed on with this information. So our developer would make our component like that.

function App() {
 return (
   <Tabbar links={[
     {title: 'First', url: "https://smashingmagazine.com/first"},
     {title: 'Second', url: '/second'}]
   } />
 )
}

This is great, but what if the user wants to render button items instead a items? Well, we could add another property that tells the component what type of element to render.

But you can see how this quickly becomes unmanageable, we will have to support more and more properties to handle different utility cases and edge cases for maximum flexibility.

Here’s a better way to use it React.cloneElement.

We start by changing our interface to refer to ReactNode type. This is a generic type that includes everything React can reproduce, typically JSX Elements, but can also be strict and even null. This is useful for indicating that you want to accept React components or JSX as inline arguments.

export interface ITabbarProps {
 links: ReactNode[]
}

Now we ask the user to give us some React Elements and we reproduce them as we want.

function Tabbar(props: ITabbarProps) {
 return (
   <>
     {props.links.map((e, i) =>
       e // simply return the element itself
     )}
   </>
 )
}

This is completely valid and will reproduce our items. But we forget a few things. For one, key! We will add keys so that React can reproduce our lists efficiently. We will also change our elements to make necessary transformations to fit into our styling, such as className, etc.

We can do these with React.cloneElementand another function React.isValidElement to check the argument is in line with what we expect!

More after the jump! Continue reading below ↓

React.isValidElement

This feature returns true if an element is a valid React Element, and React can render it. Here is an example of changing 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 add a key gear to each item we submit and make each link fat at the same time! We can now accept arbitrary React Elements as props as such:

function App() {
 return (
   <Tabbar links={[
     <a href="https://smashingmagazine.com/first">First</a>,
     <button type="button">Second</button>
   ]} />
 )
}

We can override any of the props placed on an item and easily accept different kinds of items, making our component much more flexible and easy to use.

The advantage here is if we would put a custom onClick handles to our button, we could do it. Accepting the React elements themselves as arguments is a powerful way to give flexibility to your component design.

useState Setter function

Use hooks! That useState hook is extremely useful and a great API to quickly build mode into your components like this:

const [myValue, setMyValue] = useState()

Due to JavaScript runtime it may have some hiccups. Do you remember closures?

In some situations, a variable may not be the correct value due to the context it is in, e.g. In ordinary for-loops or asynchronous events. This is due to lexical scope. When a new function is created, the lexical scope is retained. Because there is no new function, the lexical scope of newVal is not retained and the value is therefore actually settled by the time it is used.

setTimeout(() => {
 setMyValue(newVal) // this will not work
}, 1000)

What you need to do is use the setter as a function. By creating a new function, the variable reference is preserved to a lexical extent, and currentVal is submitted by the React useState Hook itself.

setTimeout(() => {
 setMyValue((currentVal) => {
   return newVal
 })
}, 1000)

This ensures that your value is updated correctly because the setter function is called in the correct context. What React is doing here is calling your function in the correct context so that React mode can be updated. This can also be used in other situations where it is useful to trade at the current value. React calls your function with the first argument as the current value.

Note: For further reading on the subject of async and closures, I recommend reading “useState Lazy Initialization And Function Updates ”by Kent C. Dodds.

JSX built-in features

Here is a Codepen demo of a JSX inline feature:

See the pen [Hello World in React](https://codepen.io/smashingmag/pen/QWgQQKR) by Gaurav Khanna.

See Pen Hello World in React by Gaurav Khanna.

Not exactly a React API per-say.

JSX supports inline functions, and it can be really useful to declare simple logic with variables inline as long as it returns a JSX element.

Here is 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 declare code inside 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 just perform some logic. Pay attention to the parentheses around the inline function. Also especially here, where we call this function, we could even send 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 the 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 a code to go through a set of numbers and then display them inline. If you use a static location generator such as Gatsby, this step would also be pre-calculated.

component extends type

This feature is extremely useful for creating auto-completion components, and allows you to create components that extend existing HTMLElements or other components. Most useful for correctly writing an element interface in Typescript, but the actual application is the same for JavaScript.

Here is a simple example, let’s say we will override one or two properties of a button element, but still allows developers to add other properties to the button. Such as setting type="button" or type="submit". Of course, we do not want to recreate the entire button element, we just want to expand its existing properties and maybe add another prop.

import React, { ButtonHTMLAttributes } from 'react'

First we import React and ButtonHTMLAttributes class, a type that includes the props for a HTMLButtonElement. You can read the source code for this type of interface here:

And you can see that the React team has re-implemented all the web APIs in TypeScript so that it can be type-checked.

Next, we declare our interface as such, and add ours status property.

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
 status?: 'primary' | 'info' | 'danger'
}

And finally, we do a few things. We use ES6 destructuring to pull out the props we care about (status, and children), and declares any other property as rest. And in our JSX output, we return a button element with ES6 structure to add 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 one type plug or other props that a button would typically have. We have provided an extra prop that we have used in className to set the button style.

Here is the whole 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 provides a great way to create reusable internal components that adhere to your style guidelines without rebuilding entire HTML elements! You can simply override entire props such as setting className based on status or allow additional class names to be submitted 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 reinsert it, with a security check in case the prop is undefined.

Conclusion

React is an extremely powerful library, and there is a good reason why it has quickly gained popularity. It gives you a great toolkit for building high-performance and easy-to-maintain web apps. It is 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 remarkable and largely overlooked. Try them in your next project!

For further reading on the latest React APIs, hooks, I would recommend reading useHooks (🐠). The Typescript Cheatsheet also has some great information for React and Typescript Hooks.

Smashing Editorial
(ks, vf, yk, il)

Leave a Comment