React Hooks

Uma nova forma (nem tão nova) de pensar

Introdução

Fala galera, beleza? Tem um tempão, mas um tempão mesmo que eu quero escrever sobre hooks e nunca consigo. Acabo lendo muitos artigos sobre e nunca escrevi um pouco da minha visão e algumas das técnicas que tenho adotado.

Sem mais delongas, vamos lá começar a escrever...

Rule of Hooks.

Antes de começar a ir lá de verdade, vamos deixar anotado as regras dos hooks, que podem ser aplicadas ao seu projeto com eslint através do pacote eslint-plugin-react-hooks. Você poder fazer um deep dive na documentação

  1. Use Hooks Apenas no Nível Superior
  2. Use Hooks Apenas Dentro de Funções do React

Mas em classes era assim

Não. Não. De novo, não. A primeira coisa que precisei fazer para ter um bom entendimento de hooks foi parar de pensar em como eu faria as coisas com classes, apesar de ambos os approaches nos entregarem componentes, temos uma diferença enorme entre eles.

Se você já conhece componentes de classes, então esqueça um pouco do ciclo de vida para entender sobre hooks. As vezes acabamos fazendo algumas associações no caso do useEffect

  • "O useEffect com um array de dependências vazio é igual ao componentDidMount"
  • "O useEffect com um array de dependências com alguns itens que precisam mudar é igual ao componentDidMount e ao componentDidUpdate"

Isso é parcialmente verdade, apesar do efeito causado ser o mesmo, não podemos assumir que são a mesma coisa. Um exemplo:

// Em caso de uma classe
componentDidMount()
{
    console.log("Componente montou")
}

// Com hooks
useEffect(() => {
    console.log("Componente montou")
}, [])

useEffect(() => {
    console.log("Componente montou ou atualizou")
}, [state])

Se formos fazer uma rápida associação a classes, nosso componente com hooks possui dois componentDidMount? Sim e não.

  • Sim. Pois ao ser montado, ambos efeitos do nosso useEffect serão executados
  • Não. Pois o nosso segundo efeito não é executado somente na hora do componente montar, ele será executado sempre que o state mudar. Ao montar o componente, o state receberá um valor inicial, logo...ele irá mudar e irá triggar nosso evento. Qualquer atualização nele irá fazer o efeito ser executado de novo.

O nosso querido hook useEffect apenas reage as mudanças dos seus dependentes.

Outro cara que confundimos é o useState por conta do método de classes this.setState(). Vamos dar uma conferida nos métodos:

// updater pode ser um objeto ou uma função
this.setState(updater[, callback
])


// demonstração de uso
class Component extends React.Component<never, { name: string }> {
    constructor(props: never) {
        super(props);
        this.state = {
            name: ""
        }
    }

    update = () => {
        this.setState({name: "Javascript"});
        this.setState(current => {
            return {name: "Typescript"};
        }, () => console.log("Atualizou com Typescript no this.state.name"));
    }
}

Importante lembrar que o this.setState atualiza seu estado de acordo com o que você retorna para ele, se você possuir 2 propriedades e o seu objeto de atualização possuir somente uma, ele não irá concatenar o estado anterior com o novo estado e nada será perdido.

Agora no nosso amigo useState funciona de forma um pouco diferente do this.setState. Vamos ver:

const [state, setState] = useState("");
// setando diretamente o valor
setState("Nova string");
setState((currentState) => "Nova string com função");

Nesse caso é de boa, mas e nesse caso:

const [state, setState] = useState < {name: string; age: number}({name: "", age: 0});
// setando diretamente o valor
setState({name: "Typescript"});

Se você fizer isso, a propriedade age será perdida e você irá ganhar um undefined, para contornar isso, basta você fazer

const [state, setState] = useState < {name: string; age: number}({name: "", age: 0});
// setando diretamente o valor
setState(currentState => ({...currentState, name: "Typescript"}));

Em minha opinião, o useState só é interessante de se utilizar nos seguintes casos:

  • Valores de tipos primários
  • Objetos que são preenchidos em uma única ação

Com o useState, podemos compor nosso estado em pequenas partes isoladas e controladas de forma isolada.

Mas Allan, eu quero manipular meu estado inteiro, como era no this.setState das classes

Bom, se você pensou isso, vou apresentar a você o useReducer. Vou apresentar duas formas, a forma tradicional e um custom hook que estou fazendo (Como eu levo mais de um dia pra escrever alguns artigos, pode ser que ele já esteja no meu git).

import React, {function useReducer<R extends React.ReducerWithoutAction<any>, I>(reducer: R, initializerArg: I, initializer: (arg: I) => React.ReducerStateWithoutAction<R>): [React.ReducerStateWithoutAction<R>, React.DispatchWithoutAction] (+4 overloads)
An alternative to `useState`. `useReducer` is usually preferable to `useState` when you have complex state logic that involves multiple sub-values. It also lets you optimize performance for components that trigger deep updates because you can pass `dispatch` down instead of callbacks.
@version16.8.0@see{@link https://react.dev/reference/react/useReducer}
useReducer
} from "react";
const
const initialState: {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
initialState
= {
name: stringname: "", age: numberage: 0, points: numberpoints: 0, isApproved: booleanisApproved: false, }; type
type Actions = {
    type: "onChangeText";
    text: string;
} | {
    type: "onChangeCheckbox";
    check: boolean;
} | {
    type: "onChangeNumber";
    value: number;
    field: "age" | "points";
}
Actions
=
| { type: "onChangeText"type: "onChangeText"; text: stringtext: string; } | { type: "onChangeCheckbox"type: "onChangeCheckbox"; check: booleancheck: boolean; } | { type: "onChangeNumber"type: "onChangeNumber"; value: numbervalue: number; field: "age" | "points"field: "age" | "points"; }; type
type State = {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
State
= typeof
const initialState: {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
initialState
;
const const reducer: (state: State, actions: Actions) => Statereducer = (
state: {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
state
:
type State = {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
State
, actions: Actionsactions:
type Actions = {
    type: "onChangeText";
    text: string;
} | {
    type: "onChangeCheckbox";
    check: boolean;
} | {
    type: "onChangeNumber";
    value: number;
    field: "age" | "points";
}
Actions
):
type State = {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
State
=> {
if (actions: Actionsactions.type: "onChangeText" | "onChangeCheckbox" | "onChangeNumber"type === "onChangeText") { return {...
state: {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
state
, name: stringname:
actions: {
    type: "onChangeText";
    text: string;
}
actions
.text: stringtext};
} if (
actions: {
    type: "onChangeCheckbox";
    check: boolean;
} | {
    type: "onChangeNumber";
    value: number;
    field: "age" | "points";
}
actions
.type: "onChangeCheckbox" | "onChangeNumber"type === "onChangeCheckbox") {
return {...
state: {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
state
, isApproved: booleanisApproved:
actions: {
    type: "onChangeCheckbox";
    check: boolean;
}
actions
.check: booleancheck};
} if (
actions: {
    type: "onChangeNumber";
    value: number;
    field: "age" | "points";
}
actions
.type: "onChangeNumber"type === "onChangeNumber") {
return {...
state: {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
state
, [
actions: {
    type: "onChangeNumber";
    value: number;
    field: "age" | "points";
}
actions
.field: "age" | "points"field]:
actions: {
    type: "onChangeNumber";
    value: number;
    field: "age" | "points";
}
actions
.value: numbervalue};
} return
state: {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
state
;
}; function function Component(): voidComponent() { const [
const state: {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
state
, const dispatch: React.Dispatch<Actions>dispatch] =
useReducer<(state: State, actions: Actions) => State>(reducer: (state: State, actions: Actions) => State, initialState: {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}, initializer?: undefined): [React.ReducerState<(state: State, actions: Actions) => State>, React.Dispatch<React.ReducerAction<(state: State, actions: Actions) => State>>] (+4 overloads)
An alternative to `useState`. `useReducer` is usually preferable to `useState` when you have complex state logic that involves multiple sub-values. It also lets you optimize performance for components that trigger deep updates because you can pass `dispatch` down instead of callbacks.
@version16.8.0@see{@link https://react.dev/reference/react/useReducer}
useReducer
(const reducer: (state: State, actions: Actions) => Statereducer,
const initialState: {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
initialState
);
const const onChange: (e: React.ChangeEvent<HTMLInputElement>) => voidonChange = (e: React.ChangeEvent<HTMLInputElement>e: React.interface React.ChangeEvent<T = Element>ChangeEvent<HTMLInputElement>) => { const {const value: string
Returns the value of the data at the cursor's current position. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
, const name: string
Sets or retrieves the name of the object.
name
, const type: string
Returns the content type of the object. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/type)
type
, const checked: boolean
Sets or retrieves the state of the check box or radio button.
checked
} = e: React.ChangeEvent<HTMLInputElement>e.React.ChangeEvent<HTMLInputElement>.target: EventTarget & HTMLInputElementtarget;
if (const type: string
Returns the content type of the object. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/type)
type
=== "checkbox") {
return const dispatch: (value: Actions) => voiddispatch({type: "onChangeCheckbox"type: "onChangeCheckbox", check: booleancheck: const checked: boolean
Sets or retrieves the state of the check box or radio button.
checked
});
} if (const type: string
Returns the content type of the object. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/type)
type
=== "number") {
return const dispatch: (value: Actions) => voiddispatch({ type: "onChangeNumber"type: "onChangeNumber", value: numbervalue: var Number: NumberConstructor
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number
.NumberConstructor.parseFloat(string: string): number
Converts a string to a floating-point number.
@paramstring A string that contains a floating-point number.
parseFloat
(const value: string
Returns the value of the data at the cursor's current position. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
),
field: "age" | "points"field: const name: string
Sets or retrieves the name of the object.
name
as "age" | "points",
}); } return const dispatch: (value: Actions) => voiddispatch({type: "onChangeText"type: "onChangeText", text: stringtext: const value: string
Returns the value of the data at the cursor's current position. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
});
}; }

Isso te lembra um pouco do redux? A diferença é que eu não usei switch/case. Pra ser sincero, eu não gosto de usar os reducers assim pois quando preciso de alguma lógica para um tipo de dispatch, eu tenho um escopo compartilhado entre as outras actions ou então tenho que criar um bloco dentro do if ou switch/case.

Bom, até aqui eu dei um leve overview de como hooks não são exatamente um as is de classes. Daqui pra frente é hora de extrair o poder que hooks nos dá com custom hooks e algumas outras técnicas

Custom hooks - useReducer

Como falei, essa forma de fazer um useReducer é estranha pra mim, gosto de transformar cada action que será despachada em uma função isolada das outras. Abaixo o código do useReducer customizado, se o código ficar muito grande, pode ver o gist

import React, {function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)
Returns a stateful value, and a function to update it.
@version16.8.0@see{@link https://react.dev/reference/react/useState}
useState
, function useMemo<T>(factory: () => T, deps: React.DependencyList): T
`useMemo` will only recompute the memoized value when one of the `deps` has changed.
@version16.8.0@see{@link https://react.dev/reference/react/useMemo}
useMemo
,
const Fragment: React.ExoticComponent<{
    children?: React.ReactNode | undefined;
}>
Lets you group elements without a wrapper node.
@see{@link https://react.dev/reference/react/Fragment React Docs}@example```tsx import { Fragment } from 'react'; <Fragment> <td>Hello</td> <td>World</td> </Fragment> ```@example```tsx // Using the <></> shorthand syntax: <> <td>Hello</td> <td>World</td> </> ```
Fragment
} from "react";
// Lembrando pro cara que não pode haver reatribuição // no estado pois ele é imutável (ou deveria ser) type type Immutable<State> = { [P in keyof Readonly<State>]?: Readonly<State>[P] | undefined; }Immutable<function (type parameter) State in type Immutable<State>State> = type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
Make all properties in T optional
Partial
<type Readonly<T> = { readonly [P in keyof T]: T[P]; }
Make all properties in T readonly
Readonly
<function (type parameter) State in type Immutable<State>State>>;
// Inferência dos tipos da função primária type type Infer<State, Fn extends (...args: never) => (state: State) => Immutable<State>> = (...args: Parameters<Fn>) => (state: State) => Immutable<State>Infer< function (type parameter) State in type Infer<State, Fn extends (...args: never) => (state: State) => Immutable<State>>State, function (type parameter) Fn in type Infer<State, Fn extends (...args: never) => (state: State) => Immutable<State>>Fn extends (...args: neverargs: never) => (state: Statestate: function (type parameter) State in type Infer<State, Fn extends (...args: never) => (state: State) => Immutable<State>>State) => type Immutable<State> = { [P in keyof Readonly<State>]?: Readonly<State>[P] | undefined; }Immutable<function (type parameter) State in type Infer<State, Fn extends (...args: never) => (state: State) => Immutable<State>>State> > = (...args: Parameters<Fn>args: type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tuple
Parameters
<function (type parameter) Fn in type Infer<State, Fn extends (...args: never) => (state: State) => Immutable<State>>Fn>) => (state: Statestate: function (type parameter) State in type Infer<State, Fn extends (...args: never) => (state: State) => Immutable<State>>State) => type Immutable<State> = { [P in keyof Readonly<State>]?: Readonly<State>[P] | undefined; }Immutable<function (type parameter) State in type Infer<State, Fn extends (...args: never) => (state: State) => Immutable<State>>State>;
// apenas um utils para extender nos tipos type type ReducerChunk<Actions, State> = { [key in keyof Actions]: (args: any) => (state: State) => Immutable<State>; }ReducerChunk<function (type parameter) Actions in type ReducerChunk<Actions, State>Actions, function (type parameter) State in type ReducerChunk<Actions, State>State> = { [function (type parameter) keykey in keyof function (type parameter) Actions in type ReducerChunk<Actions, State>Actions]: (args: anyargs: any) => (state: Statestate: function (type parameter) State in type ReducerChunk<Actions, State>State) => type Immutable<State> = { [P in keyof Readonly<State>]?: Readonly<State>[P] | undefined; }Immutable<function (type parameter) State in type ReducerChunk<Actions, State>State>; }; export type type Dispatches<State, Actions extends ReducerChunk<Actions, State>> = { [key in keyof Actions]: Infer<State, Actions[key]>; }Dispatches<function (type parameter) State in type Dispatches<State, Actions extends ReducerChunk<Actions, State>>State, function (type parameter) Actions in type Dispatches<State, Actions extends ReducerChunk<Actions, State>>Actions extends type ReducerChunk<Actions, State> = { [key in keyof Actions]: (args: any) => (state: State) => Immutable<State>; }ReducerChunk<function (type parameter) Actions in type Dispatches<State, Actions extends ReducerChunk<Actions, State>>Actions, function (type parameter) State in type Dispatches<State, Actions extends ReducerChunk<Actions, State>>State>> = { [function (type parameter) keykey in keyof function (type parameter) Actions in type Dispatches<State, Actions extends ReducerChunk<Actions, State>>Actions]: type Infer<State, Fn extends (...args: never) => (state: State) => Immutable<State>> = (...args: Parameters<Fn>) => (state: State) => Immutable<State>Infer<function (type parameter) State in type Dispatches<State, Actions extends ReducerChunk<Actions, State>>State, function (type parameter) Actions in type Dispatches<State, Actions extends ReducerChunk<Actions, State>>Actions[function (type parameter) keykey]>; }; const const useReducer: <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions) => [State, Dispatches<State, Actions>]useReducer = <function (type parameter) State in <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions): [State, Dispatches<State, Actions>]State, function (type parameter) Actions in <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions): [State, Dispatches<State, Actions>]Actions extends type ReducerChunk<Actions, State> = { [key in keyof Actions]: (args: any) => (state: State) => Immutable<State>; }ReducerChunk<function (type parameter) Actions in <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions): [State, Dispatches<State, Actions>]Actions, function (type parameter) State in <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions): [State, Dispatches<State, Actions>]State>>( initialState: StateinitialState: function (type parameter) State in <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions): [State, Dispatches<State, Actions>]State, actions: Actions extends ReducerChunk<Actions, State>actions: function (type parameter) Actions in <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions): [State, Dispatches<State, Actions>]Actions ): [function (type parameter) State in <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions): [State, Dispatches<State, Actions>]State, type Dispatches<State, Actions extends ReducerChunk<Actions, State>> = { [key in keyof Actions]: Infer<State, Actions[key]>; }Dispatches<function (type parameter) State in <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions): [State, Dispatches<State, Actions>]State, function (type parameter) Actions in <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions): [State, Dispatches<State, Actions>]Actions>] => { const [const state: Statestate, const setState: React.Dispatch<React.SetStateAction<State>>setState] = useState<State>(initialState: State | (() => State)): [State, React.Dispatch<React.SetStateAction<State>>] (+1 overload)
Returns a stateful value, and a function to update it.
@version16.8.0@see{@link https://react.dev/reference/react/useState}
useState
(initialState: StateinitialState);
// memoizando as actions para evitar novos objetos const const dispatches: Dispatches<State, Actions>dispatches = useMemo<Dispatches<State, Actions>>(factory: () => Dispatches<State, Actions>, deps: React.DependencyList): Dispatches<...>
`useMemo` will only recompute the memoized value when one of the `deps` has changed.
@version16.8.0@see{@link https://react.dev/reference/react/useMemo}
useMemo
(
() => var Object: ObjectConstructor
Provides functionality common to all JavaScript objects.
Object
.
ObjectConstructor.entries<unknown>(o: {
    [s: string]: unknown;
} | ArrayLike<unknown>): [string, unknown][] (+1 overload)
Returns an array of key/values of the enumerable properties of an object
@paramo Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
entries
(actions: Actions extends ReducerChunk<Actions, State>actions).Array<[string, unknown]>.reduce<Dispatches<State, Actions>>(callbackfn: (previousValue: Dispatches<State, Actions>, currentValue: [string, unknown], currentIndex: number, array: [...][]) => Dispatches<...>, initialValue: Dispatches<...>): Dispatches<...> (+2 overloads)
Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
reduce
(
(acc: Dispatches<State, Actions>acc, [name: stringname, dispatch: anydispatch]: [string, any]) => ({ ...acc: Dispatches<State, Actions>acc, [name: stringname]: (...params: anyparams: any) => { const const event: anyevent = dispatch: anydispatch(...params: anyparams); const setState: (value: React.SetStateAction<State>) => voidsetState((currentState: StatecurrentState) => ({ ...currentState: StatecurrentState, ...const event: anyevent(...params: anyparams), })); }, }), {} as type Dispatches<State, Actions extends ReducerChunk<Actions, State>> = { [key in keyof Actions]: Infer<State, Actions[key]>; }Dispatches<function (type parameter) State in <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions): [State, Dispatches<State, Actions>]State, function (type parameter) Actions in <State, Actions extends ReducerChunk<Actions, State>>(initialState: State, actions: Actions): [State, Dispatches<State, Actions>]Actions> ), [actions: Actions extends ReducerChunk<Actions, State>actions] ); return [const state: Statestate, const dispatches: Dispatches<State, Actions>dispatches]; }; type
type STATE = {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
STATE
= {
name: stringname: string; age: numberage: number; points: numberpoints: number; isApproved: booleanisApproved: boolean; }; const const initialState: STATEinitialState:
type STATE = {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
STATE
= {
name: stringname: "", age: numberage: 0, points: numberpoints: 0, isApproved: booleanisApproved: false, }; const const App: () => React.JSX.ElementApp = () => { const [const state: STATEstate,
const reducers: Dispatches<STATE, {
    onChangeName: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeNumber: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeCheckbox: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
}>
reducers
] =
const useReducer: <STATE, {
    onChangeName: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeNumber: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeCheckbox: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
}>(initialState: STATE, actions: {
    ...;
}) => [STATE, Dispatches<STATE, {
    ...;
}>]
useReducer
(const initialState: STATEinitialState as
type STATE = {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
STATE
, {
onChangeName: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>onChangeName: (e: React.ChangeEvent<HTMLInputElement>e: React.interface React.ChangeEvent<T = Element>ChangeEvent<HTMLInputElement>) => { const {const value: string
Returns the value of the data at the cursor's current position. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
} = e: React.ChangeEvent<HTMLInputElement>e.React.ChangeEvent<HTMLInputElement>.target: EventTarget & HTMLInputElementtarget;
// Se for passar o evento para essa próxima função // não se esqueça de usar e.persist() // mais informações: // https://reactjs.org/docs/events.html#event-pooling return (): type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
Make all properties in T optional
Partial
<
type STATE = {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
STATE
> => ({name?: string | undefinedname: const value: string
Returns the value of the data at the cursor's current position. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
});
}, onChangeNumber: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>onChangeNumber: (e: React.ChangeEvent<HTMLInputElement>e: React.interface React.ChangeEvent<T = Element>ChangeEvent<HTMLInputElement>) => { const {const name: string
Sets or retrieves the name of the object.
name
, const value: string
Returns the value of the data at the cursor's current position. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
} = e: React.ChangeEvent<HTMLInputElement>e.React.ChangeEvent<HTMLInputElement>.target: EventTarget & HTMLInputElementtarget;
return (): type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
Make all properties in T optional
Partial
<
type STATE = {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
STATE
> => ({
[const name: string
Sets or retrieves the name of the object.
name
as "age" | "points"]: var Number: NumberConstructor
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number
.NumberConstructor.parseFloat(string: string): number
Converts a string to a floating-point number.
@paramstring A string that contains a floating-point number.
parseFloat
(const value: string
Returns the value of the data at the cursor's current position. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
),
}); }, onChangeCheckbox: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>onChangeCheckbox: (e: React.ChangeEvent<HTMLInputElement>e: React.interface React.ChangeEvent<T = Element>ChangeEvent<HTMLInputElement>) => { const {const checked: boolean
Sets or retrieves the state of the check box or radio button.
checked
} = e: React.ChangeEvent<HTMLInputElement>e.React.ChangeEvent<HTMLInputElement>.target: EventTarget & HTMLInputElementtarget;
return (): type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
Make all properties in T optional
Partial
<
type STATE = {
    name: string;
    age: number;
    points: number;
    isApproved: boolean;
}
STATE
> => ({isApproved?: boolean | undefinedisApproved: const checked: boolean
Sets or retrieves the state of the check box or radio button.
checked
});
}, }); return ( <
const Fragment: React.ExoticComponent<{
    children?: React.ReactNode | undefined;
}>
Lets you group elements without a wrapper node.
@see{@link https://react.dev/reference/react/Fragment React Docs}@example```tsx import { Fragment } from 'react'; <Fragment> <td>Hello</td> <td>World</td> </Fragment> ```@example```tsx // Using the <></> shorthand syntax: <> <td>Hello</td> <td>World</td> </> ```
Fragment
>
<JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>input React.InputHTMLAttributes<HTMLInputElement>.name?: string | undefinedname="name" React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefinedonChange={
const reducers: Dispatches<STATE, {
    onChangeName: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeNumber: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeCheckbox: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
}>
reducers
.onChangeName: Infer<STATE, (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>>onChangeName} React.InputHTMLAttributes<HTMLInputElement>.value?: string | number | readonly string[] | undefinedvalue={const state: STATEstate.name: stringname}/>
<JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>input React.InputHTMLAttributes<HTMLInputElement>.type?: React.HTMLInputTypeAttribute | undefinedtype="number" React.InputHTMLAttributes<HTMLInputElement>.name?: string | undefinedname="age" React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefinedonChange={
const reducers: Dispatches<STATE, {
    onChangeName: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeNumber: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeCheckbox: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
}>
reducers
.onChangeNumber: Infer<STATE, (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>>onChangeNumber}
React.InputHTMLAttributes<HTMLInputElement>.value?: string | number | readonly string[] | undefinedvalue={const state: STATEstate.age: numberage} /> <JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>input React.InputHTMLAttributes<HTMLInputElement>.type?: React.HTMLInputTypeAttribute | undefinedtype="number" React.InputHTMLAttributes<HTMLInputElement>.name?: string | undefinedname="points" React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefinedonChange={
const reducers: Dispatches<STATE, {
    onChangeName: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeNumber: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeCheckbox: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
}>
reducers
.onChangeNumber: Infer<STATE, (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>>onChangeNumber}
React.InputHTMLAttributes<HTMLInputElement>.value?: string | number | readonly string[] | undefinedvalue={const state: STATEstate.points: numberpoints} /> <JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>input React.InputHTMLAttributes<HTMLInputElement>.type?: React.HTMLInputTypeAttribute | undefinedtype="checkbox" React.InputHTMLAttributes<HTMLInputElement>.name?: string | undefinedname="isApproved" React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefinedonChange={
const reducers: Dispatches<STATE, {
    onChangeName: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeNumber: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
    onChangeCheckbox: (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>;
}>
reducers
.onChangeCheckbox: Infer<STATE, (e: React.ChangeEvent<HTMLInputElement>) => () => Partial<STATE>>onChangeCheckbox}
React.InputHTMLAttributes<HTMLInputElement>.checked?: boolean | undefinedchecked={const state: STATEstate.isApproved: booleanisApproved} /> </
const Fragment: React.ExoticComponent<{
    children?: React.ReactNode | undefined;
}>
Lets you group elements without a wrapper node.
@see{@link https://react.dev/reference/react/Fragment React Docs}@example```tsx import { Fragment } from 'react'; <Fragment> <td>Hello</td> <td>World</td> </Fragment> ```@example```tsx // Using the <></> shorthand syntax: <> <td>Hello</td> <td>World</td> </> ```
Fragment
>
); }; export default const App: () => React.JSX.ElementApp;

Com esse useReducer nós podemos criar as funções do nosso componente no próprio useReducer e conseguimos inferir todos os tipos corretamente. Cada type do useReducer original vira uma property no nosso objeto de funções.

Sim, o useReducer foi criado com o useState. Não tá errado kkkk

Como explicado no comentário, o useMemo foi utilizado para que não seja recriado um objeto a toda renderização, somente quando as nossas funções mudarem. Uma otimização bem básica é criar o objeto de função fora do componente.

Com esse hook, acabei dando um exemplo bem consistente do useState + useMemo.

Lidando com listeners

Um coisa um pouco comum é criar um event listener, seja para um elemento ou até para o nosso objeto window. Vou demonstrar um efeito para observar a alteração de tamanho da tela

const isClient = typeof window === "object";
const getSize = () => (isClient ? window.innerWidth : 0);

const useWidth = () => {
    const [windowSize, setWindowSize] = useState(getSize);
    useEffect(() => {
        if (!isClient) {
            return;
        }
        const resizeHandler = () => setWindowSize(getSize());
        window.addEventListener("resize", resizeHandler);
        return () => window.removeEventListener("resize", handleResize);
    }, []);

    return windowSize;
};

export default useWidth;

O resizeHandler foi criado dentro do useEffect pois o addEventListener e o removeEventListener precisam da mesma referência para controlar o evento.

Uma coisa importante a falar é o retorno do useEffect. Acabei não falando anteriormente, mas o retorno do useEffect é executado quando o componente desmonta, efeito similar ao componentWillUnmount.

O que é o useCallback?

O useCallback é quase um alias para o useMemo, mas somente para funções. Ele garante a mesma referência de funções, evitando que funções no corpo dos nossos componentes de função sejam criadas a cada novo reRender.

useEffect ou useLayoutEffect?

Bom, os dois são iguais, mas diferentes. O useLayoutEffect é executado somente após todas as mutações na DOM. O ideal de seu uso é somente quando você faz mutações com refs ou coisas que dependam de elementos no nosso DOM (elementos que não são controlados por React, por exemplo).

React.forwardRef <3 useImperativeHandler

Quando você precisa passar as referências do seu componente para que irá consumir, o seu componente precisa estar envolvido por um React.forwardRef e com o useImperativeHandler nós iremos atribuir o valor de ref do nosso componente. Simples assim:

type Props = {
    ref: {
        focus(): void;
    };
};

const Input: React.FC<Props> = (props, externalRef) => {
    const internalRef = useRef();
    useImperativeHandle(externalRef, () => ({
        focus: () => {
            internalRef.current.focus();
        },
    }));
    return <input {...props}
    ref = {ref}
    />;
};

export default React.forwardRef(Input);

Eu ainda não fiz um uso muito absurdo desses 2 recursos, mas é assim que funciona e é importante você saber que ele existe e um caso de uso.

Conclusão: vou ficar devendo 2 hooks

Faltou eu apresentar o useContext e o useDebugValue. O useDebugValue eu realmente NUNCA usei graças ao nosso vício de socar console.log + debugger em tudo. Sei que é um hook que nos ajuda, mas nunca tive necessidade de fazer o uso.

Agora o useContext...fica tranquilo que eu vou fazer uma experiência bem maneira com ele e escrever um post somente sobre esse hook. Mas já adianto que podemos usar a ContextAPI (não a legada, a da versão 16.3) para substituir o uso de Redux em alguns casos.

E é isso pessoal, espero que tenham gostado. Não sei concluir esse post por que ainda queria demonstrar mais alguns casos, mas vamos com calma. Até a próxima.