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
- Use Hooks Apenas no Nível Superior
- 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 aocomponentDidMount
" - "O
useEffect
com um array de dependências com alguns itens que precisam mudar é igual aocomponentDidMount
e aocomponentDidUpdate
"
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, ostate
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.useReducer} from "react";
const const initialState: {
name: string;
age: number;
points: number;
isApproved: boolean;
}
initialState = {
name: string
name: "",
age: number
age: 0,
points: number
points: 0,
isApproved: boolean
isApproved: 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: string
text: string;
}
| {
type: "onChangeCheckbox"
type: "onChangeCheckbox";
check: boolean
check: boolean;
}
| {
type: "onChangeNumber"
type: "onChangeNumber";
value: number
value: 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) => State
reducer = (state: {
name: string;
age: number;
points: number;
isApproved: boolean;
}
state: type State = {
name: string;
age: number;
points: number;
isApproved: boolean;
}
State, actions: Actions
actions: 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: Actions
actions.type: "onChangeText" | "onChangeCheckbox" | "onChangeNumber"
type === "onChangeText") {
return {...state: {
name: string;
age: number;
points: number;
isApproved: boolean;
}
state, name: string
name: actions: {
type: "onChangeText";
text: string;
}
actions.text: string
text};
}
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: boolean
isApproved: actions: {
type: "onChangeCheckbox";
check: boolean;
}
actions.check: boolean
check};
}
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: number
value};
}
return state: {
name: string;
age: number;
points: number;
isApproved: boolean;
}
state;
};
function function Component(): void
Component() {
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.useReducer(const reducer: (state: State, actions: Actions) => State
reducer, const initialState: {
name: string;
age: number;
points: number;
isApproved: boolean;
}
initialState);
const const onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
onChange = (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 & HTMLInputElement
target;
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) => void
dispatch({type: "onChangeCheckbox"
type: "onChangeCheckbox", check: boolean
check: 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) => void
dispatch({
type: "onChangeNumber"
type: "onChangeNumber",
value: number
value: 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.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) => void
dispatch({type: "onChangeText"
type: "onChangeText", text: string
text: 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.useState, function useMemo<T>(factory: () => T, deps: React.DependencyList): T
`useMemo` will only recompute the memoized value when one of the `deps` has changed.useMemo, const Fragment: React.ExoticComponent<{
children?: React.ReactNode | undefined;
}>
Lets you group elements without a wrapper node.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 optionalPartial<type Readonly<T> = { readonly [P in keyof T]: T[P]; }
Make all properties in T readonlyReadonly<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: never
args: never) => (state: State
state: 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 tupleParameters<function (type parameter) Fn in type Infer<State, Fn extends (...args: never) => (state: State) => Immutable<State>>
Fn>) => (state: State
state: 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) key
key in keyof function (type parameter) Actions in type ReducerChunk<Actions, State>
Actions]: (args: any
args: any) => (state: State
state: 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) key
key 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) key
key]>;
};
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: State
initialState: 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: State
state, 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.useState(initialState: State
initialState);
// 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.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 objectentries(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.reduce(
(acc: Dispatches<State, Actions>
acc, [name: string
name, dispatch: any
dispatch]: [string, any]) => ({
...acc: Dispatches<State, Actions>
acc,
[name: string
name]: (...params: any
params: any) => {
const const event: any
event = dispatch: any
dispatch(...params: any
params);
const setState: (value: React.SetStateAction<State>) => void
setState((currentState: State
currentState) => ({
...currentState: State
currentState,
...const event: any
event(...params: any
params),
}));
},
}),
{} 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: State
state, const dispatches: Dispatches<State, Actions>
dispatches];
};
type type STATE = {
name: string;
age: number;
points: number;
isApproved: boolean;
}
STATE = {
name: string
name: string;
age: number
age: number;
points: number
points: number;
isApproved: boolean
isApproved: boolean;
};
const const initialState: STATE
initialState: type STATE = {
name: string;
age: number;
points: number;
isApproved: boolean;
}
STATE = {
name: string
name: "",
age: number
age: 0,
points: number
points: 0,
isApproved: boolean
isApproved: false,
};
const const App: () => React.JSX.Element
App = () => {
const [const state: STATE
state, 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: STATE
initialState 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 & HTMLInputElement
target;
// 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 optionalPartial<type STATE = {
name: string;
age: number;
points: number;
isApproved: boolean;
}
STATE> => ({name?: string | undefined
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});
},
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 & HTMLInputElement
target;
return (): type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
Make all properties in T optionalPartial<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.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 & HTMLInputElement
target;
return (): type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
Make all properties in T optionalPartial<type STATE = {
name: string;
age: number;
points: number;
isApproved: boolean;
}
STATE> => ({isApproved?: boolean | undefined
isApproved: 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.Fragment>
<JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
input React.InputHTMLAttributes<HTMLInputElement>.name?: string | undefined
name="name" React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefined
onChange={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[] | undefined
value={const state: STATE
state.name: string
name}/>
<JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
input
React.InputHTMLAttributes<HTMLInputElement>.type?: React.HTMLInputTypeAttribute | undefined
type="number"
React.InputHTMLAttributes<HTMLInputElement>.name?: string | undefined
name="age"
React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefined
onChange={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[] | undefined
value={const state: STATE
state.age: number
age}
/>
<JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
input
React.InputHTMLAttributes<HTMLInputElement>.type?: React.HTMLInputTypeAttribute | undefined
type="number"
React.InputHTMLAttributes<HTMLInputElement>.name?: string | undefined
name="points"
React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefined
onChange={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[] | undefined
value={const state: STATE
state.points: number
points}
/>
<JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
input
React.InputHTMLAttributes<HTMLInputElement>.type?: React.HTMLInputTypeAttribute | undefined
type="checkbox"
React.InputHTMLAttributes<HTMLInputElement>.name?: string | undefined
name="isApproved"
React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefined
onChange={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 | undefined
checked={const state: STATE
state.isApproved: boolean
isApproved}
/>
</const Fragment: React.ExoticComponent<{
children?: React.ReactNode | undefined;
}>
Lets you group elements without a wrapper node.Fragment>
);
};
export default const App: () => React.JSX.Element
App;
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.