loading...

TypeScript extending ReactJs component from basic

garryxiao profile image Garry Xiao Updated on ・3 min read

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')
)

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!')
}

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')
)

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')
)

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')
)

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)
    }

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

Posted on by:

garryxiao profile

Garry Xiao

@garryxiao

From China, living in NZ now, a startup founder, architect, senior software developer and team lead

Discussion

markdown guide