React APIs useful for creating flexible components with TypeScript Smashing Magazine ⋆ 4State News MO AR KS OK

Quick summary ↬

React with JSX is a fantastic tool for creating easy-to-use components. Typewritten components make integrating your components into their applications and exploring your APIs an absolute pleasure for developers. 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 directly? What about React.cloneElement? React is more than just turning your JSX into HTML. Much more, and to help you improve your knowledge of the lesser-known (but very useful) APIs that the React library ships with. We’re going to go over a few of them and some of their use cases that can dramatically improve the integration and usefulness of your components.

In this article, we’ll go over some useful React APIs that aren’t as well-known but extremely useful for web developers. Readers should be experienced with React and JSX syntax, knowledge of Typescript is helpful but not necessary. Readers will come away with everything they need to know to dramatically improve React components when used in React apps.

React.cloneElement

Most developers may never have heard of cloneElement or never used. It was introduced relatively recently to replace the now obsolete cloneWithProps function. cloneElement clone an item, it also allows you to merge new props with the existing item, modify them or replace 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 is the condensed typed version:

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

You can take an item, edit it, or even overwrite its children, and then return it as a new item. Take a look at the following example. Let’s say we want to create a Tab bar component of links. It 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) =>
       {e.title}
     )}
   
 )
}

The TabBar is a list of links, but we need a way to define two data elements, the link title and the URL. We will therefore want a data structure to be transmitted with this information. So our developer would make our component like this.

function App() {
 return (
   
 )
}

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

But you can see how cumbersome it will get quickly, we would need to support more and more properties to handle various use cases and edge cases for maximum flexibility.

Here is a better way to use React.cloneElement.

We will start by changing our interface to reference the ReactNode type. This is a generic type that encompasses anything React can render, usually JSX elements, but can also be strings and even null. This is useful for indicating that you want to accept React or JSX components as inline arguments.

export interface ITabbarProps {
 links: ReactNode[]
}

Now we’re asking the user to give us React items, and we’ll render them as we want.

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

This is perfectly valid and would make our elements. But we are forgetting a couple of things. For a, key! We want to add keys so that React can render our lists efficiently. We also want to modify our elements to make the necessary transformations so that they fit into our style, such as className, etc.

We can do it with React.cloneElement, and another function React.isValidElement to verify that the argument conforms to what we expect!

More after the jump! Read on below

React.isValidElement

This function returns true if an element is a valid React element and React can render it. Here is an example of modifying the elements of 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 prop to each item we pass on and bold each link at the same time! We can now accept arbitrary React elements as props like this:

function App() {
 return (
   First,
     
   ]} />
 )
}

We can replace any prop set on an item and easily accept different types of items, making our component much more flexible and easy to use.

The advantage here is that if we wanted to define a custom onClick manager to our button, we could do that. Accepting React elements themselves as arguments is a powerful way to give your component design flexibility.

useState Poseur function

Use hooks! The useState hook is extremely useful and is a fantastic API for quickly creating state in your components, like so:

const [myValue, setMyValue] = useState()

Due to the execution of JavaScript, there may be some issues. Do you remember the closures?

In some situations, a variable may not be the correct value due to the context in which it is located, such as in generally for loops or asynchronous events. This is due to the lexical scope. 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 in fact dereferenced when it is used.

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

What you will need to do is use the setter as a function. When creating a new function, the variable reference is kept in the lexical scope and the currentVal is passed by the React useState Hook itself.

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

This will ensure that your value is correctly updated, 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 is useful to act on the current value, React calls your function with the first argument as the current value.

To note: For further reading on the subject of async and shutdowns, I recommend that you read “useState Lazy Initialization And Function Updates ”by Kent C. Dodds.

JSX online functions

Here is a Codepen demo of a JSX inline function:

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

See the Pen Hello World in React by Gaurav Khanna.

Not exactly a React API per se.

JSX supports inline functions and it can be very useful for declaring simple logic with inline variables, as long as it returns a JSX element.

Here is an example :

function App() {
  return (
    <>
     {(() => {
       const darkMode = isDarkMode()
       if (darkMode) {
         return (
           
) } else { return (
) // we can declare JSX anywhere! } })()} // don't forget to call the function! ) }

Here we are declaring code inside JSX, we can execute arbitrary code and all we have to do is return a JSX function to render.

We can make it conditional or just apply some logic. Take note of the parentheses surrounding the inline function. Also, especially here where we call 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 inside a JSX element.

function App() {
  return (
    <>
      {(() => {
        let str=""
        for (let i = 0; i < 10; i++) {
          str += i
        }
        return (

{str}

) })()} ) }

Here we can run code to iterate through a set of numbers and then display them inline. If you are using a static site generator such as Gatsby, this step will also be pre-calculated.

component extends type

Extremely useful for creating components that are compatible with AutoComplete, this feature allows you to create components that extend the HTMLElements or other components. Mainly useful for correctly typing an element interface in Typescript, but the actual application is the same for JavaScript.

Here is a simple example, let’s say we want to override one or two properties of a button element, but still gives developers the option to add other properties to the button. Such as setting type="button" Where type="submit". We obviously don’t want to recreate the entire button element, we just want to extend its existing properties and maybe add an additional prop.

import React, { ButtonHTMLAttributes } from 'react'

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

And you can see that the React team has reimplemented all web APIs in TypeScript so that they can be verified.

Then we declare our interface like this, adding our status goods.

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

And finally, we do a couple of things. We use ES6 destructuring to extract the accessories that interest us (status, and children), and declare all other properties as rest. And in our JSX output, we return a button element, with ES6 structuring to add additional properties to that element.

function Button(props: ButtonProps) {
 const { status, children, ...rest } = props // rest has any other props
 return (
   
 )
}

So now a developer can add a type prop or any other prop that a button would usually have. We gave an additional prop that we used in the className to define the style of the button.

Here is the full example:

import React, { ButtonHTMLAttributes } from 'react'
 
export interface ButtonProps extends ButtonHTMLAttributes {
 status?: 'primary' | 'info' | 'danger'
}
 
export default function Button(props: ButtonProps) {
 const { status, children, ...rest } = props
 return (
   
 )
}

This is a great way to create reusable internal components that conform to your style guidelines without rebuilding entire HTML elements! You can simply replace entire accessories such as the definition of the className depending on the status or allow the transmission of additional class names.

import React, { ButtonHTMLAttributes } from 'react'
 
export interface ButtonProps extends ButtonHTMLAttributes {
 status?: 'primary' | 'info' | 'danger'
}
 
export default function Button(props: ButtonProps) {
 const { status, children, className, ...rest } = props
 return (
   
 )
}

Here we take the support className passed to our Button element, and reinsert it, with a safety check in case the accessory is undefined.

Conclusion

React is an extremely powerful library, and there’s a good reason it’s quickly gaining popularity. It gives you a great set of tools to build high performance and easy to maintain web applications. 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 worth noting and largely overlooked. Try them out in your next project!

To learn more about the latest React APIs, hooks, I recommend you read useHooks (🐠). The Typescript cheat sheet also has great information for React and Typescript hooks.


(ks, vf, yk, he)

Post a comment below


Source link

About the author