• Stop Building Your UI Components like this❌
  • Harsh Choudhary
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Zavier
  • Proofreader: PingHGao, Tinnkm

Stop building your components this way ❌

Indeed, everyone is happy to abstract common logic into reusable components. But a simple, sloppy abstraction can backfire, and that’s a topic for another day. Today we’ll talk about how to design truly reusable components.

We usually abstract components by defining parameters. Also, you’ve probably seen so-called “reusable” components with more than 50 parameters! Such components eventually become difficult to use and maintain, as well as causing performance problems and hard-to-track bugs.

Adding a parameter to meet a new requirement is not as simple as writing an additional if logic, and you end up adding so much code that components become very large and difficult to maintain.

However, if we design abstract components carefully, we can write components that are really easy to use and maintain, without silly bugs, and not so complex that users are discouraged.

Kent C Dodd’s has analyzed this problem in depth: Simply React

What do reusable components look like?

There is a LoginFormModal component that abstracts the modal boxes of the login and registration forms. The component itself is not that complex, accepting only a few attributes, but it is very inflexible. We wanted a more flexible component because we might need to create a lot of modal boxes in our application.

<LoginFormModal
  onSubmit={handleSubmit}
  modalTitle="Modal title"
  modalLabelText="Modal label (for screen readers)"
  submitButton={<button>Submit form</button>}
  openButton={<button>Open Modal</button>} / >Copy the code

Finally, we’ll create a component that can be used like this:

<Modal>
  <ModalOpenButton>
    <button>Open Modal</button>
  </ModalOpenButton>
  <ModalContents aria-label="Modal label (for screen readers)">
    <ModalDismissButton>
      <button>Close Modal</button>
    </ModalDismissButton>
    <h3>Modal title</h3>
    <div>Some great contents of the modal</div>
  </ModalContents>
</Modal>
Copy the code

However, it doesn’t seem more complicated in terms of the amount of code. We have given the ability to control the behavior of components to the user of the component rather than the creator, which is called inversion of control. It certainly has more code than our existing LoginFormModal component, but it’s simpler, more flexible, suitable for our future use cases, and doesn’t get any more complicated.

For example, consider a case where we don’t just want to render a form, but want to render whatever we like. Our Modal supports this, but LoginFormModal needs to accept a new parameter. Or, what if we want the close button to appear below the content? We need a special parameter called renderCloseBelow. But for our Modal, this is obviously easy to do. You simply move the ModalCloseButton component to the desired location.

More flexibility, less interface exposure.

This model is called composite components-multiple components that compose the desired UI. Typical examples are

It is widely used in many practical libraries, such as:

Let’s create our first composite component and also create a reusable Modal.

Create our first composite component

import * as React from 'react'
import VisuallyHidden from '@reach/visually-hidden'

/* Here the Dialog and CircleButton is a custom component Dialog is nothing button some styles applied on reach-dialog component provided by @reach-ui */
import {Dialog, CircleButton} from './lib'

const ModalContext = React.createContext()
//this helps in identifying the context while visualizing the component tree
ModalContext.displayName = 'ModalContext'

function Modal(props) {
  const [isOpen, setIsOpen] = React.useState(false)

  return <ModalContext.Provider value={[isOpen, setIsOpen]} {. props} / >
}

function ModalDismissButton({children: child}) {
  const [, setIsOpen] = React.useContext(ModalContext)
  return React.cloneElement(child, {
    onClick: () = > setIsOpen(false),})}function ModalOpenButton({children: child}) {
  const [, setIsOpen] = React.useContext(ModalContext)
  return React.cloneElement(child, {
    onClick: () = > setIsOpen(true),})}function ModalContentsBase(props) {
  const [isOpen, setIsOpen] = React.useContext(ModalContext)
  return (
    <Dialog isOpen={isOpen} onDismiss={()= >setIsOpen(false)} {... props} />)}function ModalContents({title, children, ... props}) {
  return (
    //we are making generic reusable component thus we allowed user custom styles
   //or any prop they want to override
    <ModalContentsBase {. props} >
      <div>
        <ModalDismissButton>
          <CircleButton>
            <VisuallyHidden>Close</VisuallyHidden>
            <span aria-hidden>x</span>
          </CircleButton>
        </ModalDismissButton>
      </div>
      <h3>{title}</h3>
      {children}
    </ModalContentsBase>)}export {Modal, ModalDismissButton, ModalOpenButton, ModalContents}
Copy the code

Yeah! We have implemented a lot of logic, now we can use the above components, for example:

<Modal>
     <ModalOpenButton>
         <Button>Login</Button>
     </ModalOpenButton>
     <ModalContents aria-label="Login form" title="Login">
         <LoginForm
            onSubmit={register}
            submitButton={<Button>Login</Button>} / ></ModalContents>
  </Modal>
Copy the code

The code is now more readable and flexible.

Allows users to pass their own onClickHandler

ModalOpenButton and ModalCloseButton set the onClick event for their child buttons so that we can open and close the modal box. However, what if the users of these components want to perform some action (in addition to opening/closing the modal box) when the user clicks the button (for example, triggering the analysis business)?

We create a callAll method that executes all the methods passed to it, as follows:

callAll(() = > setIsOpen(false), () = >console.log("I ran"))
Copy the code

I learned this from the Epic React Workshop in Kent. It’s so clever. I love it.

const callAll = (. fns) = > (. args) = > fns.forEach(fn= >fn && fn(... args))Copy the code

Let’s use it in our component:

function ModalDismissButton({children: child}) {
  const [, setIsOpen] = React.useContext(ModalContext)
  return React.cloneElement(child, {
    onClick: callAll(() = > setIsOpen(false), child.props.onClick),
  })
}

function ModalOpenButton({children: child}) {
  const [, setIsOpen] = React.useContext(ModalContext)
  return React.cloneElement(child, {
    onClick: callAll(() = > setIsOpen(true), child.props.onClick),
  })
}
Copy the code

This allows us to use the custom button passed to us by onClickHandler, as shown below:

<ModalOpenButton>
  <button onClick={()= >console.log('sending data to facebook ;) ')}>Open Modal</button>
</ModalOpenButton>
Copy the code

conclusion

Don’t rush into component abstraction and leave everything to parameters. Maybe it’s a simple component now, but you don’t know which use cases you’ll need to implement in the future. Don’t think of it as a tradeoff between time and maintainability, complexity can grow exponentially.

Take advantage of composite components in React to make your life easier.

Also, check out Kent’s Epic React Course, where I learned about Compound Components and more.

As for me, my name is Harsh and I like writing code. I’ve been doing this since I was 16. I feel right at home building Web applications with React. I am currently learning Remix.

If you like this blog, follow me! I’m planning to share more of my habits.

Twitter

Linkedin

Learn more about me: Harsh Choudhary

Feel free to read other blogs to test your hooks or how to write custom hooks.

The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.