Reactjs

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.

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

Here’s the condensed Typescript version:

1 2 3 4 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.

1 2 3 4 5 6 7 8 9 10 11 12 13 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.

1 2 3 4 5 6 7 8 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.

1 2 3 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.

1 2 3 4 5 6 7 8 9 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.

1 2 3 4 5 6 7 8 9 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:

1 2 3 4 5 6 7 8 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:

1 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.

1 2 3 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.

1 2 3 4 5 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.