DEV Community

Garry Xiao
Garry Xiao

Posted on • Updated on

TypeScript extending ReactJs component from basic

From the official article (https://reactjs.org/docs/composition-vs-inheritance.html), it recommends using composition instead of inheritance to reuse code between components. As the support of hooks with functional components, that's the trends, especially with the support of TypeScript, will make things amazing.

Start simplest example:

function TestComponent() {
  return (
    <h1>Hello, world!</h1>
  )
}

ReactDOM.render(
  <TestComponent />,
  document.getElementById('root')
)
Enter fullscreen mode Exit fullscreen mode

As you see, a functional component is just a function with return. As required, User-Defined Components Must Be Capitalized. Here JSX codes used, with TypeScript file extension 'tsx'. Each JSX element, like

<h1>Hello, world!</h1>

is just syntactic sugar for calling React.createElement(component, props, ...children), as

React.createElement('h1', null, 'Hello, world!')

. So, anything you can do with JSX can also be done with just plain JavaScript. More details please view https://reactjs.org/docs/jsx-in-depth.html. So the example below with pure TypeScript (file extension could be .ts) is equal:

function TestComponent() {
  return React.createElement('h1', null, 'Hello, world!')
}
Enter fullscreen mode Exit fullscreen mode

Property support is common. We make the example a little complex:

interface TestComponentProps {
  name?: string
}

function TestComponent(props: TestComponentProps) {
  return (
    <h1>{props.name || 'Unknown'} - Hello, world!</h1>
  )
}

ReactDOM.render(
  <TestComponent name="Garry" />,
  document.getElementById('root')
)
Enter fullscreen mode Exit fullscreen mode

Under TypeScript, we use an interface or a type to define the properties. The nullable property adds a '?' after the property name. Now the component can accept the property 'name' and change the output accordingly. You can add any other properties as you want.

It's not necessary to write everything during development. There are UI frameworks who have made a lot of effort to speed up the process. Like Material-UI (https://material-ui.com/) or Antd (https://ant.design). Just follow the documentation, understand each component, practice them, and will be handy. Then custom a component would be necessary. Here will make an extended TestComponent:

interface TestComponentProps {
  name?: string
}

function TestComponent(props: TestComponentProps) {
  return (
    <h1>{props.name || 'Unknown'} - Hello, world!</h1>
  )
}

interface TestComponentNewProps extends TestComponentProps {
  age?: number
}

function TestComponentNew(props: TestComponentNewProps) {
  return (
    <div>
      <TestComponent {...props}/>
      <h2>Age: {props.age}</h2>
    </div>
  )
}

ReactDOM.render(
  <TestComponentNew name="Garry" age="40" />,
  document.getElementById('root')
)
Enter fullscreen mode Exit fullscreen mode

How to extend a Material-UI component? We change the previous example to extend the Button component:

import React from "react"
import ReactDOM from "react-dom"
import Button, { ButtonProps } from "@material-ui/core/Button"

const TestComponentNew : React.FunctionComponent<ButtonProps> = (props) => {
  props = Object.assign({ variant: 'contained' }, props)
  return (
    <Button {...props}>{props.children}</Button>
  )
}

ReactDOM.render(
  <div>
    <Button variant="contained">Source button</Button>
    <br/>
    <TestComponentNew>Extended button</TestComponentNew>
  </div>,
  document.getElementById('root')
)
Enter fullscreen mode Exit fullscreen mode

The key point is to use 'React.FunctionComponent' to extend and pass ButtonProps as a strong type for the generic method. Then you could use props.children and other properties inside. It's impossible to set properties directly but could use Object.assign to set default value. The output is:
Alt Text

Another topic added here is ref (https://reactjs.org/docs/forwarding-refs.html). Here is a TypeScript sample to deal with it:

import React, { FormEvent } from "react"

/**
 * API form properties
 */
export interface APIFormProps {
    /**
     * Style class name
     */
    className?: string
}

/**
 * API form reference interface
 */
export interface APIFormRef {
    changeHandler(event: React.ChangeEvent<HTMLInputElement>):void
}

/**
 * API Form
 * @param props 
 * @param ref 
 */
const APIFormForward : React.ForwardRefRenderFunction<APIFormRef, React.PropsWithChildren<APIFormProps>> = (
    props,
    ref
) => {
    // hooks
    const formElement = React.useRef<HTMLFormElement | null>(null);
    React.useImperativeHandle(ref, () => ({
        changeHandler: (event: React.ChangeEvent<HTMLInputElement>) => {
            console.log(event)
        }
      }))

    return (
        <form ref={formElement} {...props}>{props.children}</form>
    )
}
export const APIForm = React.forwardRef(APIFormForward)

    // Form reference
    let formRef = React.useRef<APIFormRef>(null)

    const changeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
        // Call the method from an event handler
        formRef.current?.changeHandler(event)
    }
Enter fullscreen mode Exit fullscreen mode

By the way, https://codesandbox.io/ is a good place for practicing. That's all. Enjoy yourself on the journey!

Oldest comments (0)