// ZONA PELIGROSA
useImperativeHandle()
useEffect()
useEff this
function Component(props) {
useEffect(() => {
// Do something
return () => {/* Cleanup */}
}, [/* Dependencies */]);
// ...
return (
<div>{/* ... */}</div>
);
}
×2
(in strict mode)
useEffect()
🤔
useEffect(() => {
// componentDidMount?
}, []);
useEffect(() => {
// componentDidUpdate?
}, [something, anotherThing]);
useEffect(() => {
return () => {
// componentWillUnmount?
}
}, []);
useEffect is not a lifecycle hook.
import React, { useState, useEffect } from 'react';
function Example() {
const [value, setValue] = useState("");
const [count, setCount] = useState(-1);
useEffect(() => {
setCount(count + 1)
});
const onChange = ({ target }) => setValue(target.value);
return (
<div>
<input type="text" value={value} onChange={onChange} />
<div>Number of changes: {count}</div>
</div>
);
}
useEffect is not
a state setter.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
No dependency array!
Dependency array
useEffect(() => {
doSomething();
}, [whenever, these, things, change])
Effect
"Declarative"
"Imperative"
Dependency array
useEffect(() => {
doSomething();
return () => cleanup();
}, [whenThisChanges]);
useEffect(() => {
if (foo && bar && (baz || quo)) {
doSomething();
} else {
doSomethingElse();
}
// oops, forgot the cleanup
}, [foo, bar, baz, quo]);
useEffect(() => {
if (isOpen && component && containerElRef.current) {
if (React.isValidElement(component)) {
ionContext.addOverlay(overlayId, component, containerElRef.current!);
} else {
const element = createElement(component as React.ComponentClass, componentProps);
ionContext.addOverlay(overlayId, element, containerElRef.current!);
}
}
}, [component, containerElRef.current, isOpen, componentProps]);
useEffect(() => {
if (removingValue && !hasValue && cssDisplayFlex) {
setCssDisplayFlex(false);
}
setRemovingValue(false);
}, [removingValue, hasValue, cssDisplayFlex]);
const isVisible = useOnScreen(ref)
const { data, error, mutate, size, setSize, isValidating } = useSWRInfinite(
(...args) => getKey(...args, repo, PAGE_SIZE),
fetcher
)
const issues = data ? [].concat(...data) : []
const isLoadingInitialData = !data && !error
const isLoadingMore =
isLoadingInitialData ||
(size > 0 && data && typeof data[size - 1] === 'undefined')
const isEmpty = data?.[0]?.length === 0
const isReachingEnd = size === PAGE_SIZE
const isRefreshing = isValidating && data && data.length === size
useEffect(() => {
if (isVisible && !isReachingEnd && !isRefreshing) {
setSize(size + 1)
}
}, [isVisible, isRefreshing])
Dependences are the wrong
mental model for effects.
function Component(props) {
useEffect(() => {
// Do something
return () => {/* Cleanup */}
}, [/* Dependencies */]);
// ...
return (
<div>{/* ... */}</div>
);
}
(in strict mode)
×2
cleanup
┬─┬ノ( º _ ºノ)
Unmount (simulated)
effect
(╯°□°)╯︵ ┻━┻
Remount
effect
(╯°□°)╯︵ ┻━┻
Mount
useEffect()
useDefect()
useFoot(() => { setGun(true); 🦶🔫 });
useEffect(() => {
const sub = createThing(input).subscribe(value => {
// do something with value
});
return sub.unsubscribe;
}, [input]);
useEffect(() => {
const sub = createThing(input).subscribe(value => {
// do something with value
});
return sub.unsubscribe;
}, [input]);
const [itemData, setItemData] = useState(null);
useEffect(() => {
// Synchronize with external system
const sub = storeApi.subscribeToItem(itemId, setItemData);
// Subscription disposal
return sub.unsubscribe;
}, [itemId]); // Subscription dependency
Action effects
Activity effects
"Fire-and-forget"
Synchronized
External
system
Activity effects
Synchronized
Unmount
Remount
function Component(props) {
useEffect(() => {
// ...
return () => {/* ... */}
}, [/* ... */]);
// ...
return (
<div>{/* ... */}</div>
);
}
???
No side-effects in render
useEffect (awkward)
Outside the component?
eventHandler()
someEffect
someEffect
someEffect
someEffect
Action effects happen
outside of rendering.
<form onSubmit={event => {
// 💥 side-effect!
submitData(event);
}}>
{/* ... */}
</form>
Event handler
useEffect
(state, event) => nextState
effects...?
(state) => UI
(state, event) => (nextState, )
effects
(state, event) => nextState
🍵
Middleware, callbacks, sagas, reactions, sinks, monads (?), whenever
...which happen to be executed at the same time as event handlers.
useEffect() ➡️ eventHandler()
const [isLoading, setIsLoading] = useState(false);
const [formData, setFormData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
if (!isLoading || !formData) { return; }
let isCanceled = false;
submitData(event)
.then(() => {
if (isCanceled) { return; }
setIsLoading(false);
})
.catch(err => {
setIsLoading(false);
setError(err);
});
return () => {
isCanceled = true;
}
}, [isLoading, formData]);
<form onSubmit={event => {
setIsLoading(true);
setFormData(event);
}}>
{/* ... */}
</form>
<form onSubmit={event => {
// 💥 side-effect!
submitData(event);
}}>
{/* ... */}
</form>
const [isLoading, setIsLoading] = useState(false);
<form onSubmit={event => {
if (isLoading) { return; }
// 💥 side-effect!
submitData(event);
setIsLoading(true);
}}>
{/* ... */}
</form>
const [isLoading, setIsLoading] = useState(false);
<form onSubmit={event => {
if (isLoading) { return; }
// 💥 side-effect!
submitData(event)
.then(() => { setIsLoading(false) })
setIsLoading(true);
}}>
{/* ... */}
</form>
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
<form onSubmit={event => {
if (isLoading) { return; }
// 💥 side-effect!
submitData(event)
.then(() => { setIsLoading(false) })
.catch(err => {
setIsLoading(false);
setError(err);
});
setIsLoading(true);
}}>
{/* ... */}
</form>
const [state, send] = useCheckoutForm();
<form onSubmit={event => {
send({ type: 'submit', data: event });
}}>
{/* ... */}
</form>
useEffect() ➡️ useMemo()
function Cart() {
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(
items.reduce((currentTotal, item) => {
return currentTotal + item.price;
}, 0)
);
}, [items]);
// ...
}
function Cart() {
const [items, setItems] = useState([]);
const total = items.reduce((currentTotal, item) => {
return currentTotal + item.price;
}, 0);
// ...
}
function Cart() {
const [items, setItems] = useState([]);
const total = useMemo(
() =>
items.reduce((currentTotal, item) => {
return currentTotal + item.price;
}, 0),
[items]
);
// ...
}
useEffect() ➡️ eventHandler()
function Product({ onOpen, onClose }) {
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
if (isOpen) {
onOpen();
} else {
onClose();
}
}, [isOpen]);
return (
<div>
<button
onClick={() => {
setIsOpen(!isOpen);
}}
>
Toggle quick view
</button>
</div>
);
}
function Product({ onOpen, onClose }) {
const [isOpen, setIsOpen] = useState(false);
function toggleView() {
const nextIsOpen = !isOpen;
setIsOpen(!isOpen);
if (nextIsOpen) {
onOpen();
} else {
onClose();
}
}
return (
<div>
<button onClick={toggleView}>Toggle quick view</button>
</div>
);
}
function useToggle({ onOpen, onClose }) {
const [isOpen, setIsOpen] = useState(false);
function toggler() {
const nextIsOpen = !isOpen;
setIsOpen(nextIsOpen);
if (nextIsOpen) {
onOpen();
} else {
onClose();
}
}
return [isOpen, toggler];
}
function Product({ onOpen, onClose }) {
const [isOpen, toggler] = useToggle({ onOpen, onClose });
return (
<div>
<button onClick={toggler}>Toggle quick view</button>
</div>
);
}
useEffect() ➡️ useSyncExternalStore()
function Store() {
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {
const sub = storeApi.subscribe(({ status }) => {
setIsConnected(status === 'connected');
});
return () => {
sub.unsubscribe();
};
}, []);
// ...
}
function Product({ id }) {
const isConnected = useSyncExternalStore(
storeApi.subscribe,
() => storeApi.getStatus() === 'connected',
true
);
// ...
}
import { createMachine } from 'xstate';
import { useMachine } from '@xstate/react';
// ..
const machine = createMachine({
// ...
});
const CheckoutForm = () => {
const [state, send] = useMachine(machine);
return (
<form onSubmit={() => send('SUBMIT')}>
{state.matches('shipping') && <Shipping />}
{state.matches('billing') && <Billing />}
{state.matches('review') && <Review />}
{state.matches('receipt') && <Receipt />}
</form>
);
}
⬅ useSyncExternalStore()
npm i @xstate/react
import { createMachine, interpret } from 'xstate';
import { useMachine } from '@xstate/react';
const machine = createMachine({
initial: 'first',
states: {
first: {
entry: doSomething,
// ...
},
// ...
submitting: {
invoke: {
src: (context) => submitForm(context.data),
onDone: {
actions: logAnalytics,
target: 'submitted'
}
}
},
submitted: {
// ...
}
}
});
Entry + exit actions
Invocations (activities)
Transition actions
npm i xstate
useEffect() ➡️ renderAsYouFetch()
import { getItems } from '../storeApi';
function Store() {
const [items, setItems] = useState([]);
useEffect(() => {
let isCanceled = false;
getItems().then((data) => {
if (isCanceled) return;
setItems(data);
});
return () => {
isCanceled = true;
};
});
// ...
}
import { useLoaderData } from "@remix-run/react";
import { json } from "@remix-run/node";
import { getItems } from '../storeApi';
export const loader = async () => {
const items = await getItems();
return json(items);
}
export default function Store() {
const items = useLoaderData();
// ...
}
import { getItems } from '../storeApi';
function Store({ items }) {
// ...
}
export async function getServerSideProps() {
const items = await getItems();
return { props: { items } }
}
export default Store;
import { getItems } from '../storeApi';
import { useQuery, useQueryClient } from 'react-query';
function Store() {
const queryClient = useQueryClient()
// ...
return (
<button onClick={() => {
queryClient.prefetchQuery('items', getItems);
}}>
See items
</button>
);
}
function Items() {
const { data } = useQuery('items', getItems);
// ...
}
useEffect() ➡️ justCallIt()
function Store() {
useEffect(() => {
storeApi.authenticate();
}, []);
// ...
}
function Store() {
const didAuthenticateRef = useRef();
useEffect(() => {
if (didAuthenticateRef.current) {
return;
}
storeApi.authenticate();
didAuthenticateRef.current = true;
}, []);
}
let didAuthenticate = false;
function Store() {
useEffect(() => {
if (didAuthenticate) {
return;
}
storeApi.authenticate();
didAuthenticate = true;
}, []);
}
storeApi.authenticate();
function Store() {
// ...
}
if (typeof window !== 'undefined') {
storeApi.authenticate();
}
function Store() {
// ...
}
function renderApp() {
if (typeof window !== 'undefined') {
storeApi.authenticate();
}
appRoot.render(<Store />);
}