Class Components
What?
- class components used to be the first class citizens
- has state, props, and lifecycle methods
- error prone, unless dead simple
Why?
- legacy, mainly
- please use
FC
whenever possible - migrate class components to
FC
if possible
How?
- use arrow functions instead of methods (docs)
render
- examine
state
andprops
-> return JSX - MUST BE PURE
- can’t use hooks 😞
import React, { Component, ReactNode } from 'react'
export interface CounterProps {
defaultValue: number
}
interface CounterState {
value: number
}
export class Counter extends Component<CounterProps, CounterState> {
static defaultProps = { defaultValue: 0 }
state = { value: this.props.defaultValue }
decrement = (): void => this.setState(({ value }) => ({ value: value - 1 }))
increment = (): void => this.setState(({ value }) => ({ value: value + 1 }))
badIncrement(): void {
// `this` is not bound
this.setState(({ value }) => ({ value: value + 1 }))
}
render = (): ReactNode => {
const { value } = this.state
return (
<div>
<button type="button" onClick={this.decrement}>
-
</button>
<span> {value} </span>
<button type="button" onClick={this.increment}>
+
</button>
</div>
)
}
}
export default (
<>
<Counter />
<Counter defaultValue={5} />
</>
)
0
5
componentDidMount
- after component rendered to DOM
- use for:
- data fetching
- manipulating DOM
componentDidUpdate
- after
state
orprops
update - usually a copy of
componentDidMount
setState
- triggers render
- multiple calls are batched
- always use
setState
to update state this.state.something = 'bad'
componentWillUnmount
- before removing component from DOM
- use for:
- freeing resources
- canceling requests
Other lifecycle methods
Bitcoin Price
import React, { Component, ReactNode } from 'react'
import { BitcoinAPI } from './bitcoin-api'
export interface BitcoinProps {}
interface BitcoinState {
price: number | null
fiat: 'gbp' | 'usd'
}
export class Bitcoin extends Component<BitcoinProps, BitcoinState> {
state = { price: null, fiat: 'gbp' } as const
mounted = false
componentDidMount = (): void => {
this.mounted = true
this.updatePrice()
}
componentDidUpdate = (
prevProps: BitcoinProps,
prevState: BitcoinState,
): void => {
const { fiat } = this.state
if (prevState.fiat !== fiat) {
this.updatePrice()
}
}
componentWillUnmount = (): void => {
this.mounted = false
}
private setGbp = () => this.setState({ fiat: 'gbp' })
private setUsd = () => this.setState({ fiat: 'usd' })
private getSymbol = () => ({ gbp: '£', usd: '$' }[this.state.fiat])
private updatePrice = async () => {
this.setState({ price: null })
const { fiat } = this.state
const value = await BitcoinAPI.getPrice(fiat).catch(() => NaN)
if (this.mounted) this.setState({ fiat, price: value })
}
render = (): ReactNode => {
const { price } = this.state
return (
<div>
<button type="button" onClick={this.setGbp}>
£
</button>
<button type="button" onClick={this.setUsd}>
$
</button>
<br />
{price == null ? (
<span>loading price...</span>
) : (
<span>
{this.getSymbol()}
{price}
</span>
)}
</div>
)
}
}
export default <Bitcoin />
loading price...
- there is a subtle race-condition above; hard to resolve elegantly
- cf.
FC
style below; shorter, simpler & race-condition free
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'
import { BitcoinAPI } from './bitcoin-api'
export const Bitcoin: FC = () => {
const [fiat, setFiat] = useState<'gbp' | 'usd'>('gbp')
const setGbp = useCallback(() => setFiat('gbp'), [])
const setUsd = useCallback(() => setFiat('usd'), [])
const [price, setPrice] = useState<number | null>(null)
useEffect(() => {
let shouldUpdate = true
setPrice(null)
BitcoinAPI.getPrice(fiat)
.then((v) => shouldUpdate && setPrice(v))
.catch(() => shouldUpdate && setPrice(NaN))
return () => {
shouldUpdate = false
}
}, [fiat])
const symbol = useMemo(() => ({ gbp: '£', usd: '$' }[fiat]), [fiat])
return (
<div>
<button type="button" onClick={setGbp}>
£
</button>
<button type="button" onClick={setUsd}>
$
</button>
<br />
{price == null ? (
<span>loading price...</span>
) : (
<span>
{symbol}
{price}
</span>
)}
</div>
)
}
export default <Bitcoin />
loading price...