11 February 2021

Introduction to React (Typescript)

I. Introduction

Front development has been continuously changing, from the early days when developing multi-page applications (MPA) was the norm to today’s full rich single page application (SPA). The emergence of Ajax technique and the significant browsers performance progress have shifted the way reasoning about UI. Many frameworks came to light simplifying the UI Javascript development such as JQuery PrototypeJS and BackboneJS to name a few. Developing the Javascript server side with NodeJS marked the coming of a new era of Javascript everywhere. New frameworks emerged with component oriented architecture Angular for example. We started developing more and more complex user interfaces and moving ahead towards the current SPA architecture. Some common patterns have been reviewed and even deprecated. The recommended separation between structure styling and behavior largely adopted before is no longer the main concern. In this context React came with a new approach dealing with Javascript UI development. Based on component as first class development and putting the performance and the application scaling as main objectives to build scalable maintainable reactive applications. This article explores React with Typescript examples and aims to highlight some of its aspects. If you need deeper explanation, or help, you can visit our Design & Code Expertise and contact us !

II. React

React is an open source component based library maintained by Facebook for creating complex scalable interfaces. It adopts declarative approach dealing with UI and clean state management making code more predictable. React components are building blocks and they are written in Javascript with optional XML like syntax for templating (JSX). Components embed their display logics. They are composable to build the final UI tree.

III. Virtual DOM

Unlike the large spread approach of DOM data binding that went through MVC pattern and its variations React abstracts the DOM. It uses in-memory DOM representation called virtual DOM (VDOM). It constructs all components in this virtual DOM and surveys the application state when change detected real DOM is updated by batch mutations. DOM manipulation is a costly operation and by just mutating necessary delta React gains efficiency. React DOM abstraction permits to unify rendering and event handling. Thus React can render on client or server or mobile (using React Native).

IV. React API

In order to abstract the DOM React introduces a new API to create the virtual DOM. Like DOM tree React virtual DOM is composed of React nodes. A React node can be a primitive value (text, number) or one or more React elements. An array of React nodes is called React fragment. React exposes an API to create elements (React.createElement) and to render them (ReactDOM.render for DOM rendering).

A. Create elements

React exposes createElement method in React object. This method reproduces the native DOM elements. React supports common HTML tags allowing declarative approach for dynamic DOM building. No need to directly manipulate neither the DOM nor its Events. In addition this method accepts new custom defined components as well thus extending DOM native behaviors.

  • React.createElement(type, props, children)

1 – type: the type of React elements. It can be:

  • built-in HTML tag (common HTML tags support
  • custom component (function or class component)

2 – props : read only properties supplied by the parent to the child element. It forms a super-set of the HTML attributes.
3 – children; any single or array of React node

  • Built-in HTML support

The following example uses React API to construct native HTML tags:

const h1Props = {style: {color: 'green'}};
const pProps = {style: {color: 'gray'}};
const h1 = createElement('h1', h1Props, 'Title');
const p = createElement('p', pProps, 'paragraph');
const builtInHTMLFragment = [h1, p];
  • Custom component

Alongside with reproducing native HTML tags React extends them with new components. In the first versions, the custom component was defined by React.createClass (deprecated way). With ES6 and Typescript classical support this is done by extending Component (class component) or using a bare function (function component).

Both class and function components accept custom properties (Props). Properties are assumed to be read only and they can have default values.

To highlight both of two component styles let’s define the following Props interface:

export interface PostProps {
title: string,
content: string
}

Note that React with Typescript can infer automatically types but in the following examples types will be explicitly declared for clarifications purposes.

  • Class component

 The class component version is created by extending the React Component class (Component or PureComponent). The only required method is render that returns the one or more React nodes.

export class PostComponent extends Component<PostProps> {
render(): ReactNode {
const title = createElement('h1', {style: {color: 'green'}}, this.props.title);
const content = createElement('p', {style: {color: 'gray'}}, this.props.content);
return [title, content];
}
}
let postClassComponent = createElement(PostComponent, {title: 'Post', content: 'Post class component example'});

React components can have default properties. To add default properties in class component PostProps can be updated like the following:

export interface PostProps {
title: string,
content?: string // make it optional
}

The optional content attribute can be initialized directly inside render method or added as static default attribute:

static defaultProps = {
content: 'NA'
};
  • Function component

The function version relies on a function that returns a React element (FunctionComponent or FC alias). The React function component does not hold state that’s why it is considered stateless. In addition it should not imply a side effect. In fact having side effects inside a function component cannot be too predictable as the component rendering is under React control and the function gets called each time React renders the component. (With React Hooks this is not the case: function component can be stateful and/or with side effects)

export const PostFunctionalComponent: FunctionComponent<PostProps> = ({title, content}: PostProps): ReactElement => {
const h1 = createElement('h1', {style: {color: 'green'}}, title);
const p = createElement('p', {style: {color: 'gray'}}, content);
return createElement(Fragment, null, [h1, p]);
};
let postFunctionalComponent = createElement(PostFunctionalComponent, {title: 'Post', content: 'Post class component example'});

Default properties can be directly applied in function signature.

export const PostFunctionalComponent: FunctionComponent<PostProps> = ({title, content = 'NA'}: PostProps): ReactElement => {
const h1 = createElement('h1', {style: {color: 'green'}}, title);
const p = createElement('p', {style: {color: 'gray'}}, content);
return createElement(Fragment, null, [h1, p]);
};

B. Render elements

To render elements the ReactDOM.render method takes place. It accepts a React element and the target DOM node (and an optional callback). Note that React renders one root node with its sub tree. Whether it renders a built-in HTML or custom component the signature is the same.

The component render method should be kept minimal because it is reevaluated every time React re-renders the component, any unnecessary code should be moved outside.

  • Built-in HTML support
ReactDOM.render(builtInHTMLFragment, document.body);
  • Custom component
ReactDOM.render(postClassComponent, document.body);
ReactDOM.render(postFunctionalComponent, document.body);

Note that rendering to document.body is discouraged as it can interfere with third-party scripts or browser extensions. Moreover it is recommended to add key property to each component, this allows React to track component instances specially inside an array (This is highlighted by React warning messages in the console). These two considerations are not taken into account in this article for demonstration purposes.


V. JSX/TSX

JSX is an optional XML like syntax. It simplifies React element creation. JSX compiles (transpiles) to React native API (React.createElement) but it is less verbose. Browsers don’t understand JSX by default so to use it compilers like Babel should be added. Typescript comes with JSX support out of the box (TSX). JSX embeds XML like in Javascript thus allowing us to better grasp the visual UI part. Take for example the following link:

const title = 'Go';
const url = 'http://www...';
const link = <a href={url}>{title}</a>;

JSX can embed advanced Javascript code enclosed in {} or nested components as highlighted in the following example:

const posts = [{
title: 'Post 1',
content: 'P1 content'
},{
title: 'Post 2',
content: 'P2 content'
}];
ReactDOM.render(<div>{posts
.map((p) => <PostComponent title={p.title} content={p.content}/>)
}</div>, document.body);

With JSX support the previous component definitions can be rewritten like below:

  • Built-in HTML support
const h1 = <h1 style={{color: 'green'}}>title</h1>;
const p = <p style={{color: 'gray'}}>paragraph</p>;
const builtInHTMLFragment = [h1, p];
  • Custom component
    • Class component
export class PostComponent extends Component<PostProps> {
render(): JSX.Element {
return <>
<h1 style={{color: 'green'}}>this.props.title</h1>
<p style={{color: 'gray'}}>this.props.content</p>
</>
}
}

Note that the render method should return one single JSX element. So an enclosing div or React.Fragment or empty tag should be added.

  • Function component
<h1 style={{color: 'green'}}>{title}</h1>
<p style={{color: 'gray'}}>{content}</p>
</>;

Note the use of React with Typescript is destructuring assignment of PostProps argument.

VI. React components

React components are building reusable and composable blocks. Each component follows the React lifecycle. React allows hooking inside the components lifecycle to control them. A component receives data through its props and can hold state. Thus it can be stateless or stateful.

A. Lifecycle

The React component lifecycle is composed of multiple phases: Initialization Mounting Updating and Unmounting.

a. Initialization (Props and States)

The first component lifecycle phase is the initialization responsible of creating and initializing the component. During initialization Props can be prefilled with default values (with Typescript this can be done by adding static attributes). The state can be initialized in the class constructor. For function component the state can be added with a React Hook (cf React.useState)

b. Mounting

The mounting phase is responsible for attaching the React component to the DOM tree. Its methods are called only once before initial rendering.

i. Component will mount

During the mounting phase the componentWillMount method is called after the constructor and before initial rendering. Any state update inside this method does not trigger re-rendering. So this method can be a perfect place to update the component state just before rendering (making API call for example). Note this method is deprecated in newer React versions thus not recommended at all any necessary state initialization can be done inside the constructor and any side effects can be added to componentDidMount.

ii. Rendering

The same render method described before.

iii. Component did mount

The componentDidMount method is called after rendering. Inside this method the component is attached to the DOM and ready for use. So any further DOM manipulations can take place here. Furthermore this method is a perfect match to integrate React with other libraries (JQuery for example).

c. Updating

The React component reacts to the props and the state changes by updating the component and its children accordingly. React offers methods to hook into the updating process.

i. Component will receive props

Whenever the props change the componentWillReceiveProps method is called. Note this method will not be called during the first rendering (mounting phase). This method gives an opportunity to react to the incoming props without additional rendering. Note this method is deprecated in new React versions (use static getDerivedStateFromProps instead).

ii. Should component update

Once the props or the state have changed the method shouldComponentUpdate is called to determine whether the component should update or not. Returning false in this method bypasses the component rendering.

iii. Component will update

The componentWillUpdate method is called just before the rendering of the component. This method is not called on initial rendering. It can be used to make additional callbacks based on both the next props and the next state but it should not mutate the state. Note this method is deprecated in newer React versions (use getSnapshotBeforeUpdate instead).

iv. Rendering

The same render method described before.

v. Component did update

The method componentDidUpdate is called after the component’s updates are flushed to the DOM. Further DOM manipulations can be performed inside this function.

d. Unmounting

React provides the componentWillUnmount method that is called just before the component gets removed from the DOM. This method is a perfect match to add cleanup code. Take for example the following cases:

  • Event listeners unbinding
  • Timers cleanup
  • Observable unsubscription

B. Stateless

A stateless component does not manage any internal state whether it is a function component or class one. For class component using pure component instructs React to apply optimization when rendering the component. To make a function component benefit from React pure component optimization a high order component (HOC) can be used to dynamically transform into a pure component (cf React.memo).

C. Stateful

A stateful component manages its internal state. The state is handled inside this.state. It should not be altered directly but using the setState method to keep it under React control. To add behaviors and interactions components should hold states. Both class and function components can now hold state (With the addition of React Hooks function components can be stateful cf React.useState).

Note that the setState method does not replace the state but merges it. Changes are not applied immediately but enqueued in the React update queue. Take for example the following stateful example:

interface LiveComponentState {
age: number
}
type Timeout = NodeJS.Timeout;
export class LiveComponent extends Component<{}, LiveComponentState> {
private timeout: Timeout | undefined;
constructor(props: {}) {
super(props);
this.state = {age: 0};
}
resetAge: MouseEventHandler<HTMLButtonElement> = (e: MouseEvent<HTMLButtonElement>) => {
this.setState({age: 0})
};
render(): React.ReactNode {
return (
<div>
<span>Component up : {this.state.age} seconds </span>
<button onClick={this.resetAge}>Reset</button>
</div>
);
}
componentDidMount(): void {
this.timeout = setInterval(
() => this.setState({age: this.state.age + 1})
, 1000);
}
componentWillUnmount(): void {
if (this.timeout)
clearTimeout(this.timeout);
}
}

VII. React event handling

React supports common DOM native events. They are wrapped with synthetic events providing cross browser compatibility. The synthetic event has the same interface as the browser’s native event (methods like preventDefault or stopPropagation still accessible) and exposes the native event via the nativeEvent property. Unlike native Javascript event handlers React uses camel-case syntax so the onclick is written as onClick. In addition event handler should be a function name not a string:

<button onclick=”onClickHandler()” />

Should be written as follows :

<button onClick={onClickHandler} />

In React application the preventDefault method should be called explicitly to avoid default HTML behavior.

export class TestLink extends Component {
constructor(props: {}) {
super(props);
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = function (e: MouseEvent<HTMLAnchorElement>) {
e.preventDefault();
// @ts-ignore
console.log(this);
};
render(): React.ReactNode {
return <a href='”#”' onClick={this.onClickHandler}> go to </a>;
}
}

Note the use of binding in the constructor to assign this context to the function. If we use an arrow function there is no need to bind this context.

onClickHandler = (e: MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
// @ts-ignore
console.log(this);
};

React events can be handled locally by the particular component or propagated to the parent component. Props can contain callback functions thus allowing the child component to communicate with its parent. To let a child component communicate with its ancestor parents the component callback function should traverse the entire inheritance tree (React provides a particular Hook to make shared context present without chaining through the top down props (cf React.useContext). State containers Redux for example offer another way to communicate between components).

In React data flow in one way from the parent to the child component (unidirectional data flow). To highlight the data flow and component interaction take for example the following expirable button that controls the click number and to disabled once the retry limit supplied by its parent is breached:

interface ExpirableButtonProps {
retry: number,
onClick: (e: MouseEvent<HTMLButtonElement>, click: number) => void
}
interface ExpirableButtonState {
retries: number,
inactive: boolean
}
export class ExpirableButton extends Component<ExpirableButtonProps, ExpirableButtonState> {
constructor(props: Readonly<ExpirableButtonProps>) {
super(props);
this.state = {retries: 0, inactive: false};
}
onClickHandler = (e: MouseEvent<HTMLButtonElement>) => {
const count = this.state.retries + 1;
if (count <= this.props.retry)
this.props.onClick(e, this.props.retry - count);
this.setState({retries: count, inactive: count === this.props.retry});
};
render(): React.ReactNode {
return <button onClick={this.onClickHandler}
disabled={this.state.inactive}>{this.props.children}</button>
}
}

Consider the following parent component to test the expirable button:

interface AppState {
message: string
}
export class App extends Component<{}, AppState> {
private retryLimit: number = 3;
constructor(props: {}) {
super(props);
this.state = {message: ''};
}
onClickHandler = (e: MouseEvent<HTMLButtonElement>, remaining: number) => {
let message = remaining > 0 ? ` You still have ${remaining} times` : ' No more times';
this.setState({message: message});
};
render(): React.ReactNode {
return (
<div>
<span>
<ExpirableButton retry={this.retryLimit}
onClick={(e: MouseEvent<HTMLButtonElement>, remaining: number) => this.onClickHandler(e, remaining)}>
Try it
</ExpirableButton>
</span>
<span>{this.state.message}</span>
</div>
)
}
}

In addition to Typescript static type control React provides a mechanism to validate component props PropTypes. The previous example does not control retry signs for example. We can add a validator to ensure the retry parameter is strictly positive. We can achieve that with the following code:

(ExpirableButton as any).propTypes = {
retry: function (props: any, propName: any, componentName: any) {
if (props[propName] < 0) {
return new Error('Invalid prop `' + propName + '` supplied to' + ' `' + componentName + '`. Retry prop should be >0');
}
},
onClick: PropTypes.func.isRequired
};

Note that making the onClick required is just for demonstration purposes as it is not mandatory because it is already checked during the compilation by Typescript.

To combine both React prop-types and Typescript we can use InferProps as follows:

const expirableButtonPropTypes = {
retry: function (props: { [key: string]: any }, propName: string, componentName: string) {
if (props[propName] < 0) {
return new Error('Invalid prop `' + propName + '` supplied to' + ' `' + componentName + '`. Retry should be >0');
}
},
onClick: PropTypes.func.isRequired
};
export class ExpirableButton extends Component<InferProps<typeof ExpirableButton.propTypes>, ExpirableButtonState> {
static propTypes = expirableButtonPropTypes;
..
}

VIII. Conclusion

React simplifies the UI development through its declarative UI component approach. Relying on the virtual DOM permits efficient complex interface building and provides cross browser compatibility out of the box. Its one way data flow eases the reasoning about the application. Using React in conjunction with Typescript is beneficial for both static typing advantages and React capabilities. In real world application Typescript type inference is good enough to relax heavy typing. This article highlighted some of the React aspects with TypeScript. Further topics could be explored to gain more grasp of the React ecosystem. High order component (HOC) React Hooks or React Redux integration to name a few.

Expertise Design & Code

$

Python, Java, C++, C# et Front-End

Découvrez nos formations

$

Parcourir le catalogue

Boostez votre carrière !

$

Nos offres pour Développeurs

Every month, receive our latest posts ! !

Try X4B now !

Discover XComponent for Business for free. Our BizDevOps software solution!

Écrit par Alaeddine Tlijani

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *