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>
);
}
}
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>
);
}
Componentes funcionais
Não possui métodos de ciclo de vida
Componentes não possuem estado
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
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
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
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>
);
}
É executado toda vez que o component é renderizado
O local ideal para controlar os 'sideEffects'
Serve ao mesmo propósito que os métodos:
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>
);
}
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>
);
}
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>
);
}
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>
);
};
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
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
// 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>
);
}
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
};
};
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>
);
}
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');
});
React Salvador Meetup #11
React Salvador Meetup #11
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.
React Salvador Meetup #11
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
Não chame hooks dentro de funções JavaScript comuns.
Hooks podem ser chamados dentro de outros hooks.
React Salvador Meetup #11
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
React Salvador Meetup #11
// 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
// 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
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
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
React Salvador Meetup #11
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
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