React Hooks!
React Hooks!
Stateful Components
ES6 classes
Métodos de ciclo de vida
Estado do componente
class Clock extends React.Component {
state = { date: new Date() };
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({ date: new Date() });
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Stateless Components
Componentes funcionais
Não possui métodos de ciclo de vida
Componentes não possuem estado
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
Stateless Components
Componentes funcionais
Não possui métodos de ciclo de vida
Componentes não possuem estado
Hooks
Hooks
O que é e
para que serve?
Facilitar o reuso de lógica entre componentes
Simplificar o código de componentes complexos
Diminuir o uso de classes
Permitir que componentes funcionais tenham estado e ciclo de vida
Não precisamos mais nos preocupar com escopo e uso de this
React Salvador Meetup #11
Hooks
import React, { useState } from 'react';
function Exemplo() {
// Declara um novo estado chamado "count"
const [count, setCount] = useState(0);
return (
<div>
<p>Você clicou {count} vezes</p>
<button onClick={() => setCount(count + 1)}>
Clique aqui
</button>
</div>
);
}
import React, { useState } from 'react';
function Exemplo() {
// Declara um novo estado chamado "count"
const [count, setCount] = useState(0);
return (
<div>
<p>Você clicou {count} vezes</p>
<button onClick={() => setCount(count + 1)}>
Clique aqui
</button>
</div>
);
}
import React from 'react';
class Exemplo extends React.Component {
state = { count: 0 };
incrementCount = () => {
this.setState(state => ({
count: state.count + 1
}));
};
render() {
const { count } = this.state;
return (
<div>
<p>Você clicou {count} vezes</p>
<button onClick={this.incrementCount}>
Clique aqui
</button>
</div>
);
}
}
import { CurrentUserConsumer } from './current-user';
import ErrorHandlers from './error-handlers';
const CollectionEditorContainer = ({
api, children, initialCollection
}) => (
<ErrorHandlers>
{errorFuncs => (
<CurrentUserConsumer>
{currentUser => (
<CollectionEditor
{...{ api, currentUser, initialCollection }}
{...errorFuncs}
>
{children}
</CollectionEditor>
)}
</CurrentUserConsumer>
)}
</ErrorHandlers>
);
import { useCurrentUser } from './current-user';
import useErrorHandlers from './error-handlers';
const CollectionEditorContainer = ({
api, children, initialCollection
}) => {
const { currentUser } = useCurrentUser();
const errorFuncs = useErrorHandlers();
return (
<CollectionEditor
{...{ api, currentUser, initialCollection }}
{...errorFuncs}
>
{children}
</CollectionEditor>
);
};
import { CurrentUserConsumer } from './current-user';
import ErrorHandlers from './error-handlers';
import Uploader from './includes/uploader';
import { NotificationConsumer } from './notifications';
const TeamEditorContainer = ({
api, children, initialTeam
}) => (
<ErrorHandlers>
{errorFuncs => (
<Uploader>
{uploadFuncs => (
<NotificationConsumer>
{notificationFuncs => (
<CurrentUserConsumer>
{(currentUser, fetched, { update }) => (
<TeamEditor
{...{ api, currentUser, initialTeam }}
updateCurrentUser={update}
{...uploadFuncs}
{...errorFuncs}
{...notificationFuncs}
>
{children}
</TeamEditor>
)}
</CurrentUserConsumer>
)}
</NotificationConsumer>
)}
</Uploader>
)}
</ErrorHandlers>
);
import { useCurrentUser } from './current-user';
import useErrorHandlers from './error-handlers';
import { useNotifications } from './notifications';
import useUploader from './includes/uploader';
const TeamEditorContainer = ({
api, children, initialTeam
}) => {
const { currentUser, update } = useCurrentUser();
const uploadFuncs = useUploader();
const notificationFuncs = useNotifications();
const errorFuncs = useErrorHandlers();
return (
<TeamEditor
{...{ api, currentUser, initialTeam }}
updateCurrentUser={update}
{...uploadFuncs}
{...notificationFuncs}
{...errorFuncs}
>
{children}
</TeamEditor>
);
};
Bundle menor
=
Maior performance
Principais Hooks
useState
useEffect
useContext
useReducer
Permite injetar estado no componente
Permite executar 'side effects' em um componente
Permite o componente se inscrever em um contexto React
Permite gerenciar estados complexos em um componente através de reducers
Dan's Quotes
Each Render Has Its Own Props and State
Each Render Has Its Own Event Handlers
Each Render Has Its Own Effects
Each Render Has Its Own... Everything
useState
Indicado para armazenar valores primitivos, nada muito complexo
Retorna sempre um valor e uma função para atualização do estado
Adiciona state a um Stateless Component
Componente Funcional
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count - 1)}>
-
</button>
<button onClick={() => setCount(count + 1)}>
+
</button>
</div>
);
}
import React from 'react';
class Counter extends React.Component {
state = {
count: 0
};
decrement = () => {
this.setState(state => ({ count: state.count - 1 }));
};
increment = () => {
this.setState(state => ({ count: state.count + 1 }));
};
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.decrement}>
-
</button>
<button onClick={this.increment}>
+
</button>
</div>
);
}
}
import React from 'react';
class Counter extends React.Component {
state = {
count: 0
};
decrement = () => {
this.setState(state => ({ count: state.count - 1 }));
};
increment = () => {
this.setState(state => ({ count: state.count + 1 }));
};
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.decrement}>
-
</button>
<button onClick={this.increment}>
+
</button>
</div>
);
}
}
import React, { useState } from 'react';
function Counter() {
// const [count, setCount] = useState(0);
const state = useState(0);
const count = state[0];
const setCount = state[1];
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count - 1)}>
-
</button>
<button onClick={() => setCount(count + 1)}>
+
</button>
</div>
);
}
import React, { useState } from 'react';
function MultipleStatesExample() {
const [vehicle, setVehicle] = useState('car');
const [foodList, setFoodList] = useState(['pizza', 'hotdog', 'kebab']);
const [buttons, setButtons] = useState([
{ text: 'Change Vehicle', doAction: () => setVehicle('bike') },
{ text: 'Change FoodList', doAction: () => setFoodList(['hamburguer']) },
]);
return (
<div>
<p>Your vehicle is a <strong>{vehicle.toUpperCase()}</strong></p>
<p>Your favorite foods:</p>
<ul>
{foodList.map(item => <li>{item}</li>)}
</ul>
{buttons.map(bt => (
<button onClick={bt.doAction}>{bt.text}</button>
))}
</div>
);
}
useEffect
É executado toda vez que o component é renderizado
O local ideal para controlar os 'sideEffects'
Serve ao mesmo propósito que os métodos:
- componentDidMount
- componentDidUpdate
- componentWillUnmount
import React, { useEffect } from 'react';
function Lyrics() {
// Similar ao componentDidMount e componentDidUpdate:
useEffect(() => {
document.title = 'São Salvador - Dorival Caymmi';
});
return (
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
);
}
Em todo render o useEffect será executado (inclusive no primeiro)
Por ser declarado dentro do componente, useEffect tem acesso a todos os states e props
import React from 'react';
class Lyrics extends React.Component {
componentDidMount() {
document.title = 'São Salvador - Dorival Caymmi';
}
render() {
return (
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
);
}
}
import React, { useEffect } from 'react';
export function Letra() {
useEffect(() => {
document.title = 'São Salvador - Dorival Caymmi';
});
return (
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
);
}
import React, { useEffect, useState } from 'react';
const originalPageTitle = document.title;
function Lyrics() {
const [title] = useState('São Salvador - Dorival Caymmi');
useEffect(() => {
document.title = `${title} | ${originalPageTitle}`;
// Similar ao componentWillUnmount:
return () => {
document.title = originalPageTitle;
};
});
return (
<div>
<h1>{title}</h1>
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
</div>
);
}
Retorna o título original quando o componente é desmontado
import React from 'react';
const originalPageTitle = document.title;
class Lyrics extends React.Component {
state = {
title: 'São Salvador - Dorival Caymmi',
};
componentDidMount() {
document.title = `${this.state.title} | ${originalPageTitle}`;
}
componentWillUnmount() {
document.title = originalPageTitle;
}
render() {
return (
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
);
}
}
import React, { useEffect, useState } from 'react';
const originalPageTitle = document.title;
function Lyrics() {
const [title] = useState('São Salvador - Dorival Caymmi');
useEffect(() => {
document.title = `${title} | ${originalPageTitle}`;
return () => {
document.title = originalPageTitle;
};
});
return (
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
);
}
import React, { useEffect, useState } from 'react';
const originalPageTitle = document.title;
function Lyrics() {
const [title] = useState('São Salvador - Dorival Caymmi');
useEffect(() => {
document.title = `${title} | ${originalPageTitle}`;
return () => {
document.title = originalPageTitle;
};
// Evita que o side effect seja executado em todo render
}, []);
return (
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
);
}
import React from 'react';
const originalPageTitle = document.title;
class Lyrics extends React.Component {
state = {
title: 'São Salvador - Dorival Caymmi',
};
componentDidMount() {
document.title = `${this.state.title} | ${originalPageTitle}`;
}
componentWillUnmount() {
document.title = originalPageTitle;
}
shouldComponentUpdate() {
return false;
}
render() {
return (
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
);
}
}
import React, { useEffect, useState } from 'react';
const originalPageTitle = document.title;
function Lyrics() {
const [title] = useState('São Salvador - Dorival Caymmi');
useEffect(() => {
document.title = `${title} | ${originalPageTitle}`;
return () => {
document.title = originalPageTitle;
};
// Evita que o side effect seja executado em todo render
}, []);
return (
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
);
}
import React, { useEffect, useState } from 'react';
const originalPageTitle = document.title;
function Lyrics() {
const [title] = useState('São Salvador - Dorival Caymmi');
useEffect(() => {
document.title = `${title} | ${originalPageTitle}`;
return () => {
document.title = originalPageTitle;
};
// O side effect só será executado quando title mudar
}, [title]);
return (
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
);
}
import React from 'react';
const originalPageTitle = document.title;
class Lyrics extends React.Component {
state = {
title: 'São Salvador - Dorival Caymmi',
};
componentDidMount() {
document.title = `${this.state.title} | ${originalPageTitle}`;
}
componentWillUnmount() {
document.title = originalPageTitle;
}
componentDidUpdate(prevProps, prevState) {
if (prevState.title !== this.state.title) {
document.title = `${this.state.title} | ${originalPageTitle}`;
}
}
render() {
return (
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
);
}
}
import React, { useEffect, useState } from 'react';
const originalPageTitle = document.title;
function Lyrics() {
const [title] = useState('São Salvador - Dorival Caymmi');
useEffect(() => {
document.title = `${title} | ${originalPageTitle}`;
return () => {
document.title = originalPageTitle;
};
}, [title]);
return (
<p>
São Salvador, Bahia de São Salvador<br />
A terra de Nosso Senhor<br />
Pedaço de terra que é meu<br />
...
</p>
);
}
useContext
Espera que seja passado como parâmetro o object criado pelo React.createContext();
Retorna o valor atual do contexto passado
O mesmo comportamento do Context.Consume porém sem o Hadouken do Ryu
import { CurrentUserConsumer } from './current-user';
import ErrorHandlers from './error-handlers';
const CollectionEditorContainer = ({
api, children, initialCollection
}) => (
<ErrorHandlers>
{errorFuncs => (
<CurrentUserConsumer>
{currentUser => (
<CollectionEditor
{...{ api, currentUser, initialCollection }}
{...errorFuncs}
>
{children}
</CollectionEditor>
)}
</CurrentUserConsumer>
)}
</ErrorHandlers>
);
import React, { useContext } from 'react';
import { CurrentUserContext } from './current-user';
import { ErrorHandlersContext } from './error-handlers';
const CollectionEditorContainer = ({
api, children, initialCollection
}) => {
const currentUser = useContext(CurrentUserContext);
const errorFuncs = useContext(ErrorHandlersContext);
return (
<CollectionEditor
{...{ api, currentUser, initialCollection }}
{...errorFuncs}
>
{children}
</CollectionEditor>
);
};
useReducer
Eu ouvi redux? 🤐
Alternativa ao useState quando se tem uma lógica complexa de estado do componente.
API semelhante ao Redux (reducer, initialState, dispatch)
Diferentemente do Redux, não permite connect (o state é local)
Pode ser combinado com a Context API para compartilhamento de estado entre componentes
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter({ initialState }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: { state.count }
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}
React Salvador Meetup #11
useReducer
Você pode criar o initialState de forma dinâmica (Lazy initialization)
function init(initialCount) {
return { count: initialCount };
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
function Counter({ initialCount }) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: { state.count }
<button
onClick={() => dispatch({ type: 'reset', payload: initialCount })}>
Reset
</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}
Permite extrair a lógica de criação do initialState para fora do reducer, diminuindo o acoplamento.
React Salvador Meetup #11
useReducer
Ao retornar o mesmo valor como estado atual para um hook useReducer, o React ignorará e não será feito um novo render e nem disparando os side effects
// TodosApp.js
export const TodosDispatch = React.createContext(null);
function TodosApp() {
// `dispatch` não muda entre re-renders
const [todos, dispatch] = useReducer(todosReducer);
return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
);
}
// ------------------------------------------------------
// DeepChild.js
function DeepChild(props) {
// Para dispararmos uma action, obtemos o dispatch do contexto.
const dispatch = useContext(TodosDispatch);
function handleClick() {
dispatch({ type: 'add', text: 'hello' });
}
return (
<button onClick={handleClick}>Add todo</button>
);
}
Custom Hooks
Permite o compartilhamento de recursos
Pode ser utilizado dentro dele todos os outros hooks
É apenas uma função que encapsula uma funcionalidade podendo ou não ter 'sideEffects'
Assim como os outros hooks, só pode ser utilizado dentro de componentes funcionais
import { useState, useEffect } from 'react';
export function useCounter(initialCount) {
const [count, setCount] = useState(initialCount);
const handleIncrement = () => setCount(prevCount => prevCount + 1);
const handleDecrement = () => setCount(prevCount => prevCount - 1);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return {
count,
setCount,
increment: handleIncrement,
decrement: handleDecrement
};
};
Fluxo de execução dos Hooks
Inicialização
Primeira renderização
Atualização
Destruição
let firstRender = true;
function RenderFunctionComponent() {
let initName;
if(firstRender){
[initName] = useState("Rudi");
firstRender = false;
}
const [firstName, setFirstName] = useState(initName);
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
Testing Hooks
import { useState, useEffect } from 'react';
export function useCounter(initialCount) {
const [count, setCount] = useState(initialCount);
const handleIncrement = () => setCount(count + 1);
const handleDecrement = () => setCount(count - 1);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return {
count,
setCount,
increment: handleIncrement,
decrement: handleDecrement
};
};
import React from 'react';
import { useCounter } from './useCounter';
export function Counter() {
const counter = useCounter(0);
return (
<div>
<p>You clicked {counter.count} times</p>
<button id="decrement" onClick={counter.decrement}>
-
</button>
<button id="increment" onClick={counter.increment}>
+
</button>
</div>
);
}
import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import { Counter } from './Counter';
let container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
it('can render and update a counter', () => {
act(() => {
ReactDOM.render(<Counter />, container);
});
const incrementButton = container.querySelector('button#increment');
const decrementButton = container.querySelector('button#decrement');
const label = container.querySelector('p');
expect(label.textContent).toBe('You clicked 0 times');
expect(document.title).toBe('You clicked 0 times');
act(() => {
incrementButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
expect(label.textContent).toBe('You clicked 1 times');
expect(document.title).toBe('You clicked 1 times');
act(() => {
decrementButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
expect(label.textContent).toBe('You clicked 0 times');
expect(document.title).toBe('You clicked 0 times');
});
it('can render and update a counter', () => {
act(() => {
ReactDOM.render(<Counter />, container);
});
const incrementButton = container.querySelector('button#increment');
const decrementButton = container.querySelector('button#decrement');
const label = container.querySelector('p');
expect(label.textContent).toBe('You clicked 0 times');
expect(document.title).toBe('You clicked 0 times');
act(() => {
incrementButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
expect(label.textContent).toBe('You clicked 1 times');
expect(document.title).toBe('You clicked 1 times');
act(() => {
decrementButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
expect(label.textContent).toBe('You clicked 0 times');
expect(document.title).toBe('You clicked 0 times');
});
Quando usar classes
Quando sua classe depender dos seguintes métodos do ciclo de vida do componente:
- getSnapshotBeforeUpdate
- componentDidCatch
React Salvador Meetup #11
E os HOC's e Render Props? 🤔
E os HOC's e Render Props? 🤔
Para a grande maioria dos casos, Hooks devem substituir esses padrões.
Não há real necessidade de reescrever seus hocs e render props components.
Mas sua aplicação se beneficiará de um código mais simples, legível e com bundle menor.
React Salvador Meetup #11
E static typing? (TS / Flow)
Hooks são apenas funções.
Isto permite adicionar tipos muito mais facilmente que HOC's.
As versões mais recentes de Flow e TypeScritpt já suportam hooks.
Resumo
- Completamente opt-in. Você pode testar sem precisar reescrever seu código atual.
- Você sequer precisa aprender hooks caso não queira.
- 100% backwards-compatible. Sem breaking changes.
- Hooks está disponível a partir do React 16.8.0.
- Não há planos para remover classes do React.
- Hooks não vai substituir o que você já sabe sobre React.
- Hooks provê uma API mais direta aos conceitos que você já conhece do React: props, state, context, refs, e lifecycle.
- Completamente opt-in. Você pode testar sem precisar reescrever seu código atual.
- Você sequer precisa aprender hooks caso não queira.
- 100% backwards-compatible. Sem breaking changes.
- Hooks está disponível a partir do React 16.8.0.
- Não há planos para remover classes do React.
- Hooks não vai substituir o que você já sabe sobre React.
- Hooks provê uma API mais direta aos conceitos que você já conhece do React: props, state, context, refs, e lifecycle.
React Salvador Meetup #11
Regras Básicas
Hooks devem estar sempre no level mais alto
Não chame um hook dentro de lops, condições ou funções aninhadas.
Hooks precisam ser chamados sempre na mesma ordem a cada nova renderização.
Isso permitirá que o state seja preservado quando tiver múltiplos states e effects.
React Salvador Meetup #11
Regras Básicas
Hooks devem ser chamados somente dentro de componentes funcionais
Não chame hooks dentro de funções JavaScript comuns.
Hooks podem ser chamados dentro de outros hooks.
React Salvador Meetup #11
Outros Hooks
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
React Salvador Meetup #11
Outros Hooks
useCallback
// returns a memoized callback
// useCallback(fn: () => void, inputs: [])
// equivalent to useMemo(() => fn, inputs);
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
React Salvador Meetup #11
Outros Hooks
useMemo
// returns a memoized value
// useMemo(() => fn: () => any, inputs: []);
function computeExpensiveValue(a: number, b: number): number {
// very expensive
return a + b;
}
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b),
[a, b]
);
React Salvador Meetup #11
Outros Hooks
useRef
function TextInputWithFocusButton() {
const inputEl = useRef(null);
return (
<div>
<input ref={inputEl} type="text" />
<button onClick={() => { inputEl.current.focus(); }}>
Focus the input
</button>
</div>
);
}
React Salvador Meetup #11
Outros Hooks
useImperativeHandle
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
// Should be used with forwardRef
FancyInput = forwardRef(FancyInput);
React Salvador Meetup #11
Outros Hooks
useLayoutEffect
React Salvador Meetup #11
Outros Hooks
useDebugValue
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// Show a label in DevTools next to this Hook
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
React Salvador Meetup #11
Referências e Docs
https://reactjs.org/docs/hooks-intro.html – Documentação Oficial
https://overreacted.io/ – Dan Abramov blog
https://kentcdodds.com/blog – Kent C. Dodds blog
https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e – React hooks: not magic, just arrays
React Salvador Meetup #11
React Hooks
By Tarun Sharma
React Hooks
Componentes funcionais de volta ao jogo!
- 330